thisisdev commited on
Commit
0ae84b2
Β·
verified Β·
1 Parent(s): e6fe3bf
Files changed (1) hide show
  1. src/streamlit_app.py +525 -2
src/streamlit_app.py CHANGED
@@ -1,13 +1,536 @@
1
  import os
2
- import time
3
  import json
 
4
  import pytz
5
  import PyPDF2
6
  import requests
7
  import streamlit as st
 
8
  from agno.agent import Agent
 
9
  from phi.tools.zoom import ZoomTool
10
  from agno.tools.email import EmailTools
11
  from datetime import datetime, timedelta
12
  from agno.models.openai import OpenAIChat
13
- from typing import Literal, Tuple, Dict, Optional
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import json
3
+ import time
4
  import pytz
5
  import PyPDF2
6
  import requests
7
  import streamlit as st
8
+
9
  from agno.agent import Agent
10
+ from phi.utils.log import logger
11
  from phi.tools.zoom import ZoomTool
12
  from agno.tools.email import EmailTools
13
  from datetime import datetime, timedelta
14
  from agno.models.openai import OpenAIChat
15
+ from streamlit_pdf_viewer import pdf_viewer
16
+ from typing import Literal, Tuple, Dict, Optional
17
+
18
+ # Class for controlling zoom access
19
+ class CustomZoomTool(ZoomTool):
20
+ def __init__(self, *, account_id: Optional[str] = None, client_id: Optional[str] = None,
21
+ client_secret: Optional[str] = None, name: str = "zoom_tool"):
22
+ super().__init__(account_id=account_id, client_id=client_id, client_secret=client_secret, name=name)
23
+ self.token_url = "https://zoom.us/oauth/token"
24
+ self.access_token = None
25
+ self.token_expires_at = 0
26
+
27
+ # This method fetches and returns a valid Zoom access token, either from cache or by making an API call.
28
+ def get_access_token(self) -> str:
29
+ # If token exists and hasn't expired, return it (avoids unnecessary API calls).
30
+ if self.access_token and time.time() < self.token_expires_at:
31
+ return str(self.access_token)
32
+ # Sets up headers and payload for the Zoom OAuth token request.
33
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
34
+ data = {
35
+ "grant_type": "account_credentials",
36
+ "account_id": self.account_id
37
+ }
38
+ # Makes a POST request to Zoom's token endpoint using the client secret for authentication.
39
+ try:
40
+ resp = requests.post(
41
+ self.token_url,
42
+ headers= headers,
43
+ data = data,
44
+ auth = (self.client_secret)
45
+ )
46
+
47
+ resp.raise_for_status()
48
+
49
+ token_info = resp.json()
50
+
51
+ self.access_token = token_info["access_token"]
52
+ expires_in = token_info["expires_in"]
53
+ self.token_expires_at = time.time() + expires_in + 60
54
+
55
+ self._set_parent_token(str(self.access_token))
56
+ return str(self.access_token)
57
+ # Logs and safely handles any network or response issues.
58
+ except requests.RequestException as e:
59
+ logger.error(f"Error Fetching access token: {e}")
60
+ return ""
61
+
62
+ def _set_parent_token(self, token: str) -> None:
63
+ """
64
+ Helper Function to set the token in parent ZoomTool class
65
+ """
66
+ if token:
67
+ self._ZoomTool_access_token = token
68
+
69
+ # ROLE Requirements as a constant dictionary
70
+
71
+ ROLE_REQUIREMENTS: Dict[str, str] = {
72
+ "ai_ml_engineer": """
73
+ Required Skills:
74
+ - Python, Pytorch/Tensorflow
75
+ - Machine Learning Algorithms and Frameworks
76
+ - Deep Learning and Neural Networks
77
+ - Data Preprocessing and Analysis
78
+ - MLOps and Model Deployment
79
+ - RAG, LLMs, Finetuning and Prompt Engineering
80
+ """,
81
+ "frontend_engineer":"""
82
+ Required Skills:
83
+ - React/Vue.js/Angular
84
+ - HTML5, CSS3, JavaScript/TypeScript
85
+ - Responsive Design
86
+ - State Management
87
+ - Frontend Testing
88
+ """,
89
+ "backend_engineer": """
90
+ Required Skills:
91
+ - Python/Java/Node.js
92
+ - REST APIs
93
+ - Database Design and Management
94
+ - System Architecture
95
+ - Cloud Services (AWS/GCP/Azure)
96
+ - Kubernetes, Docker, CI/CD
97
+ """
98
+ }
99
+
100
+ # safely initialize only the required keys in st.session_state with default values, preventing errors during use in a Streamlit app.
101
+ def init_session_state() -> None:
102
+ """Initialize only the necessary session state variables."""
103
+ defaults = {
104
+ "candidate_email": "",
105
+ "openai_api_key" : "",
106
+ "resume_text": "",
107
+ "analysis_complete": False,
108
+ "is_selected": False,
109
+ "zoom_account_id": "",
110
+ "zoom_client_id": "",
111
+ "zoom_client_secret": "",
112
+ "email_sender": "",
113
+ "email_passkey": "",
114
+ "company_name": "",
115
+ "current_pdf": None
116
+ }
117
+
118
+ for key, value in defaults.items():
119
+ if key not in st.session_state:
120
+ st.session_state[key] = value
121
+
122
+ # This function returns a function if the API is already initialized
123
+ def create_resume_analyzer() -> Agent:
124
+ """Creates and returns a resume analysis agent"""
125
+ if not st.session_state.openai_api_key:
126
+ st.error("Please enter your OpenAI API Key before procedding!")
127
+ return None
128
+ return Agent(
129
+ model = OpenAIChat(id="gpt-4.1-nano", api_key = st.session_state.openai_api_key),
130
+ description = "You are a expert Technical Recruiter who analyzes resumes",
131
+ instructions=[
132
+ "Analyze the resume against the provided job requirements",
133
+ "Be linient with AI/ML candidates who show strong potential",
134
+ "Consider project experience as valid experience",
135
+ "Value hands-on-experience with key technologies",
136
+ "Return the result in a JSON response with selection decision and feedback"
137
+ ],
138
+ markdown=True
139
+ )
140
+
141
+ def create_email_agent() -> Agent:
142
+ return Agent(
143
+ model = OpenAIChat(
144
+ id = "gpt-4.1-nano",
145
+ api_key=st.session_state.openai_api_key
146
+ ),
147
+ description="You are a expert technical recruiter coordinator handling email communications.",
148
+ instructions=[
149
+ "Draft and send professional recruitment emails",
150
+ "Act like a huma writing an email and eliminate unnecessary capital letters",
151
+ "Maintain a friendly yet professional tone",
152
+ f"Always end the mail with exactly: 'Best Regards\nTeam HR at {st.session_state.company_name}",
153
+ "Never include the sender's or receiver's name in the signature",
154
+ f"The name of the company is {st.session_state.company_name}"
155
+ ],
156
+ markdown=True,
157
+ show_tool_calls=True
158
+ )
159
+
160
+ def create_scheduler_agent() -> Agent:
161
+ zoom_tools = CustomZoomTool(
162
+ account_id = st.session_state.zoom_account_id,
163
+ client_id = st.session_state.zoom_client_id,
164
+ client_secret = st.session_state.zoom_client_secret
165
+ )
166
+
167
+ return Agent(
168
+ name = "Interview Scheduler",
169
+ model = OpenAIChat(
170
+ id = "gpt-4o-mini",
171
+ api_key = st.session_state.openai_api_key
172
+ ),
173
+ tools = [zoom_tools],
174
+ description = "You are an interview scheduling coordinator",
175
+ instructions = [
176
+ "You are an expert at scheduling technical interviews using zoom",
177
+ "Schedule interviews during business hours (9AM - 5PM IST)",
178
+ "Create meetings with proper titles and descriptions",
179
+ "Ensure all meeting details are included in responses",
180
+ "Use ISO 8601 format for dates",
181
+ "Handle scheduling errors gracefully"
182
+ ],
183
+ markdown=True,
184
+ show_tool_calls=True
185
+ )
186
+
187
+ def extract_text_from_pdf(pdf_file) -> str:
188
+ try:
189
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
190
+ text = "" # Initialize the variable here
191
+ for page in pdf_reader.pages:
192
+ page_text = page.extract_text()
193
+ if page_text: # Some pages may return None
194
+ text += page_text
195
+ return text
196
+ except Exception as e:
197
+ st.error(f"Error while parsing PDF File: {str(e)}")
198
+ return ""
199
+
200
+
201
+ def analyze_resume(resume_text: str,
202
+ role: Literal["ai_ml_engineer", "frontend_engineer", "backend_engineer"],
203
+ analyzer: Agent) -> Tuple[bool, str]:
204
+ try:
205
+ resp = analyzer.run(
206
+ f"""Please analyze this resume against the following requirements and provide your response in valid JSON format:
207
+ Role Requirements:
208
+ {ROLE_REQUIREMENTS[role]}
209
+ Resume Text:
210
+ {resume_text}
211
+
212
+ Your response must be a valid JSON object, just like this:
213
+ {{
214
+ "selected": true,
215
+ "feedback": "The resume shows strong alignment with the AI/ML Engineer role, particularly in TensorFlow and Python.",
216
+ "matching_skills": ["Python", "TensorFlow", "Scikit-learn"],
217
+ "missing_skills": ["Kubernetes", "Docker"],
218
+ "experience_level": "mid"
219
+ }}
220
+
221
+ Evaluation Criteria:
222
+ 1. Match at least 70% of required skills.
223
+ 2. Consider both theoritical knowledge and pratical knowledge.
224
+ 3. Value project experience and real-world applications.
225
+ 4. Consider transferrable skills from similar technologies.
226
+ 5. Look for evidence for continuous learning and adaptability.
227
+ Important: Return ONLY the JSON object without any formatting or backticks.
228
+ """
229
+ )
230
+
231
+ assistant_message = next((msg.content for msg in resp.messages if msg.role == "assistant"), None)
232
+
233
+ if not assistant_message:
234
+ raise ValueError("No assistant message found in the response")
235
+ result = json.loads(assistant_message.strip())
236
+ if not isinstance(result, dict) or not all(k in result for k in ["selected", "feedback"]):
237
+ raise ValueError("Invalid Response Format")
238
+ return result["selected"], result["feedback"]
239
+ except (json.JSONDecodeError, ValueError) as e:
240
+ st.error(f"Error, while decoding JSON or due to format: {str(e)}")
241
+ return False, f"Error while analyzing resume: {str(e)}"
242
+
243
+ def send_selection_email(email_agent: Agent, to_email: str, role: str) -> None:
244
+ """
245
+ Send a selection email with a congratuations.
246
+ """
247
+ email_agent.run(
248
+ f"""
249
+ Send an email to {to_email} regarding their selection for the {role} position.
250
+ The email should:
251
+ 1. Congratulate them on being selected.
252
+ 2. Explain the next steps in the process.
253
+ 3. Mention that they will receive interview details shortly.
254
+ 4. The name of the company is {st.session_state.company_name}.
255
+ """
256
+ )
257
+
258
+ def send_rejection_email(email_agent: Agent, to_email : str, role : str, feedback : str) -> None:
259
+ """
260
+ Send a rejection mail with constructive feedback
261
+ """
262
+ email_agent.run(
263
+ f"""
264
+ Send an email to {to_email} regarding their application for the {role} position.
265
+ Use this specific style:
266
+ 1. Avoid unnecessary capital letters.
267
+ 2. Be empathetic and human
268
+ 3. Mention specific feedback from: {feedback}
269
+ 4. Encourage them to upskill and try again
270
+ 5. Suggest some learning resources based on missing skills.
271
+ 6. End the email with exactly:
272
+ Best Regards,
273
+ Team HR at {st.session_state.company_name}
274
+ Do not include any name in the signature.
275
+ The tone should be like a human writing a quick but thoughtful email.
276
+ """
277
+ )
278
+
279
+ def schedule_interview(scheduler: Agent, candidate_email: str, email_agent: Agent, role: str) -> None:
280
+ """
281
+ Schedule interview during business hours (9 AM - 5 PM IST)
282
+ """
283
+ try:
284
+ # getting the current time
285
+ ist_tz = pytz.timezone("Asia/Kolkata")
286
+ current_time_ist = datetime.now(ist_tz)
287
+
288
+ tomorrow_ist = current_time_ist + timedelta(days=1)
289
+ interview_time = tomorrow_ist.replace(hour=11, minute=0, second=0, microsecond=0)
290
+ formatted_time = interview_time.strftime("%Y-%m-%dT%H:%M:%S")
291
+
292
+ meeting_resp = scheduler.run(
293
+ f"""Schedule a 60-minute technical interview with these specifications:
294
+ - Title: '{role} Technical Interview'
295
+ - Date: {formatted_time}
296
+ - Timezone: IST (Indian Standard Time)
297
+ - Attendee: {candidate_email}
298
+
299
+ Important Notes:
300
+ - The meeting must be between 9 AM - 5 PM IST
301
+ - Use IST (UTC+5:30) timezone for all communications
302
+ - Include timezone information in the meeting details
303
+ - Ask him to be confident and not so nervous and prepare well for the interview
304
+ - Also, include a small joke or sarcasm to make him relax.
305
+ """
306
+ )
307
+
308
+ st.success("Interview Scheduled Successfully! Check your email for details.")
309
+ except Exception as e:
310
+ logger.error(f"Error scheduling Interview: {str(e)}")
311
+ st.error("Unable to schedule interview. Please try again")
312
+
313
+ def main() -> None:
314
+ st.title("HeyHR Aide 🏒")
315
+
316
+ init_session_state()
317
+
318
+ with st.sidebar:
319
+ st.header("Configurations")
320
+
321
+ # OpenAI configurations
322
+ st.subheader("OpenAI Configurations")
323
+ api_key = st.text_input("OpenAI API Key", placeholder="API key here", type="password", value=st.session_state.openai_api_key, help="Get your OpenAI API Key from platform.openai.com")
324
+ if api_key: st.session_state.openai_api_key = api_key
325
+
326
+ # Zoom Settings
327
+ st.subheader("Zoom Configurations")
328
+ zoom_account_id = st.text_input("Zoom Account ID", type="password", value=st.session_state.zoom_account_id)
329
+
330
+ zoom_client_id = st.text_input("Zoom Client ID", type="password", value=st.session_state.zoom_client_id)
331
+
332
+ zoom_client_secret = st.text_input("Zoom Client Secret", type="password", value=st.session_state.zoom_client_secret)
333
+
334
+ # Email Settings
335
+ email_sender = st.text_input("Sender Email", value=st.session_state.email_sender, help="Email address to send from")
336
+
337
+ email_passkey = st.text_input("Enter Email App Password", value=st.session_state.email_passkey, type="password", help="App-specific password for email")
338
+
339
+ company_name = st.text_input("Company Name", value=st.session_state.company_name, help = "Name to use in email communications")
340
+
341
+ if zoom_account_id: st.session_state.zoom_account_id = zoom_account_id
342
+ if zoom_client_id: st.session_state.zoom_client_id = zoom_client_id
343
+ if zoom_client_secret: st.session_state.zoom_client_secret = zoom_client_secret
344
+
345
+ if email_sender: st.session_state.email_sender = email_sender
346
+ if email_passkey: st.session_state.email_passkey = email_passkey
347
+ if company_name: st.session_state.company_name = company_name
348
+
349
+ required_configs = {
350
+ "OpenAI API Key": st.session_state.openai_api_key,
351
+ "Zoom Account ID": st.session_state.zoom_account_id,
352
+ "Zoom Client ID": st.session_state.zoom_client_id,
353
+ "Zoom Client Secret": st.session_state.zoom_client_secret,
354
+ "Email Sender": st.session_state.email_sender,
355
+ "Email Password": st.session_state.email_passkey,
356
+ "Company Name": st.session_state.company_name
357
+ }
358
+
359
+ missing_config = [k for k, v in required_configs.items() if not v]
360
+ if missing_config:
361
+ st.warning(f"Pleas configure the following in the sidebar: {', '.join(missing_config)}")
362
+ return
363
+ if not st.session_state.openai_api_key:
364
+ st.warning("Please enter your OpenAI API Key in the sidebar to continue")
365
+ return
366
+
367
+ role = st.selectbox("Select the role you're applying for: ", ["ai_ml_engineer", "frontend_engineer", "backend_engineer"])
368
+
369
+ with st.expander("View Required Skills", expanded = True): st.markdown(ROLE_REQUIREMENTS[role])
370
+
371
+ # Add a "New Application" button before the resume upload
372
+ if st.button("New Application πŸ”"):
373
+ # Clear all the application related status
374
+ keys_to_clear = ["resume_text", "analysis_complete", "is_selected", "candidate_email", "current_pdf"]
375
+
376
+ for key in keys_to_clear:
377
+ if key in st.session_state:
378
+ st.session_state[key] = None if key == "current_pdf" else ""
379
+ st.rerun()
380
+
381
+ resume_file = st.file_uploader("Upload your resume (PDF)", type = ["pdf"], key = "resume_uploaded")
382
+
383
+ if resume_file is not None and resume_file != st.session_state.get("current_pdf"):
384
+ st.session_state.current_pdf = resume_file
385
+ st.session_state.resume_text = ""
386
+ st.session_state.analysis_complete = False
387
+ st.session_state.is_selected = False
388
+ st.rerun()
389
+
390
+ if resume_file:
391
+ st.subheader("Uploaded Resume")
392
+ col1, col2 = st.columns([4, 1])
393
+
394
+ with col1:
395
+ import tempfile, os
396
+ with tempfile.NamedTemporaryFile(delete = False, suffix=".pdf") as tmp_file:
397
+ tmp_file.write(resume_file.read())
398
+ tmp_file_path = tmp_file.name
399
+ resume_file.seek(0)
400
+ try:
401
+ pdf_viewer(tmp_file_path)
402
+ finally:
403
+ os.unlink(tmp_file_path)
404
+ with col2:
405
+ st.download_button(label="Download",
406
+ data = resume_file,
407
+ file_name=resume_file.name,
408
+ mime="application/pdf")
409
+ # Process the resume text
410
+ if not st.session_state.resume_text:
411
+ with st.spinner("Processing your resume..."):
412
+ resume_text = extract_text_from_pdf(resume_file)
413
+ if resume_text:
414
+ st.session_state.resume_text = resume_text
415
+ st.success("Resume Processed Successfully!")
416
+ else:
417
+ st.error("Could not process the PDF. Please try again")
418
+
419
+ # Email input with session state
420
+ email = st.text_input(
421
+ "Candidate's Email Address",
422
+ value=st.session_state.candidate_email,
423
+ key = "email_input"
424
+ )
425
+
426
+ st.session_state.candidate_email = email
427
+
428
+ # Analysis and next steps
429
+ if st.session_state.resume_text and email and not st.session_state.analysis_complete:
430
+ if st.button("Analyze Resume"):
431
+ with st.spinner("Analyzing the resume..."):
432
+ resume_analyzer = create_resume_analyzer()
433
+ email_agent = create_email_agent()
434
+
435
+ if resume_analyzer and email_agent:
436
+ print("DEBUG: Starting Resume Analysis")
437
+ is_selected, feedback = analyze_resume(
438
+ st.session_state.resume_text,
439
+ role,
440
+ resume_analyzer
441
+ )
442
+ print(f"DEBUG: Analysis Complete\n----------\nSelected: {is_selected}\nFeedback: {feedback}")
443
+
444
+ if is_selected:
445
+ st.success("Congratulations! Your skills match our requirements.")
446
+ st.session_state.analysis_complete = True
447
+ st.session_state.is_selected = True
448
+ st.rerun()
449
+ else:
450
+ st.warning("Your skillset are amazing, but unfortunately we have to move forward with other candidates as it doesn't match our requirements")
451
+ st.write(f"Feedback: {feedback}")
452
+
453
+ # Send Rejection mail
454
+ with st.spinner("Sending Feedback Mail.."):
455
+ try:
456
+ send_rejection_email(
457
+ email_agent = email_agent,
458
+ to_email = email,
459
+ role = role,
460
+ feedback = feedback
461
+ )
462
+ st.info("We've sent you a email with detailed feedback.")
463
+ except Exception as e:
464
+ logger.error(f"Error sending rejection mail: {str(e)}")
465
+ st.error("Could not send the email. Please try again.")
466
+
467
+ if st.session_state.get('analysis_complete') and st.session_state.get('is_selected', False):
468
+ st.success("Congratulations! Your skills match our requirements.")
469
+ st.info("Click 'Proceed with Application' to continue with the interview process.")
470
+
471
+ if st.button("Proceed with Application", key="proceed_button"):
472
+ print("DEBUG: Proceed button clicked") # Debug
473
+ with st.spinner("πŸ”„ Processing your application..."):
474
+ try:
475
+ print("DEBUG: Creating email agent") # Debug
476
+ email_agent = create_email_agent()
477
+ print(f"DEBUG: Email agent created: {email_agent}") # Debug
478
+
479
+ print("DEBUG: Creating scheduler agent") # Debug
480
+ scheduler_agent = create_scheduler_agent()
481
+ print(f"DEBUG: Scheduler agent created: {scheduler_agent}") # Debug
482
+
483
+ # 3. Send selection email
484
+ with st.status("πŸ“§ Sending confirmation email...", expanded=True) as status:
485
+ print(f"DEBUG: Attempting to send email to {st.session_state.candidate_email}") # Debug
486
+ send_selection_email(
487
+ email_agent,
488
+ st.session_state.candidate_email,
489
+ role
490
+ )
491
+ print("DEBUG: Email sent successfully") # Debug
492
+ status.update(label="βœ… Confirmation email sent!")
493
+
494
+ # 4. Schedule interview
495
+ with st.status("πŸ“… Scheduling interview...", expanded=True) as status:
496
+ print("DEBUG: Attempting to schedule interview") # Debug
497
+ schedule_interview(
498
+ scheduler_agent,
499
+ st.session_state.candidate_email,
500
+ email_agent,
501
+ role
502
+ )
503
+ print("DEBUG: Interview scheduled successfully") # Debug
504
+ status.update(label="βœ… Interview scheduled!")
505
+
506
+ print("DEBUG: All processes completed successfully") # Debug
507
+ st.success("""
508
+ πŸŽ‰ Application Successfully Processed!
509
+
510
+ Please check your email for:
511
+ 1. Selection confirmation βœ…
512
+ 2. Interview details with Zoom link πŸ”—
513
+
514
+ Next steps:
515
+ 1. Review the role requirements
516
+ 2. Prepare for your technical interview
517
+ 3. Join the interview 5 minutes early
518
+ """)
519
+
520
+ except Exception as e:
521
+ print(f"DEBUG: Error occurred: {str(e)}") # Debug
522
+ print(f"DEBUG: Error type: {type(e)}") # Debug
523
+ import traceback
524
+ print(f"DEBUG: Full traceback: {traceback.format_exc()}") # Debug
525
+ st.error(f"An error occurred: {str(e)}")
526
+ st.error("Please try again or contact support.")
527
+
528
+ # Reset button
529
+ if st.sidebar.button("Reset Application"):
530
+ for key in st.session_state.keys():
531
+ if key != 'openai_api_key':
532
+ del st.session_state[key]
533
+ st.rerun()
534
+
535
+ if __name__ == "__main__":
536
+ main()