Sa-m commited on
Commit
69d3350
·
verified ·
1 Parent(s): 471097e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -176
app.py CHANGED
@@ -4,13 +4,12 @@ import os
4
  import tempfile
5
  import PyPDF2
6
  import google.generativeai as genai
7
- import tensorflow as tf
8
  from transformers import BertTokenizer, TFBertModel
9
  import numpy as np
10
  import speech_recognition as sr
11
- from gtts import gTTS
12
- import pygame
13
- import io
14
  import time
15
  from dotenv import load_dotenv
16
 
@@ -18,42 +17,68 @@ from dotenv import load_dotenv
18
  load_dotenv()
19
 
20
  # Configure Generative AI
21
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) # Use environment variable or set a default
22
- text_model = genai.GenerativeModel("gemini-2.5-flash")
23
-
24
- # Load BERT model and tokenizer
25
- model = TFBertModel.from_pretrained("bert-base-uncased")
26
- tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
 
 
 
 
 
 
 
27
 
28
  # --- Helper Functions (Logic from Streamlit) ---
29
 
30
  def getallinfo(data):
 
 
31
  text = f"""{data} is given by the user. Make sure you are getting the details like name, experience,
32
  education, skills of the user like in a resume. If the details are not provided return: not a resume.
33
  If details are provided then please try again and format the whole in a single paragraph covering all the information. """
34
- response = text_model.generate_content(text)
35
- response.resolve()
36
- return response.text
 
 
 
 
37
 
38
  def file_processing(pdf_file_path): # Takes file path now
39
- with open(pdf_file_path, "rb") as f: # Open file from path
40
- reader = PyPDF2.PdfReader(f)
41
- text = ""
42
- for page in reader.pages:
43
- text += page.extract_text()
44
- return text
 
 
 
 
45
 
46
  def get_embedding(text):
47
- encoded_text = tokenizer(text, return_tensors="tf", truncation=True, padding=True) # Add padding/truncation
48
- output = model(encoded_text)
49
- embedding = output.last_hidden_state[:, 0, :]
50
- return embedding
 
 
 
 
 
 
 
 
 
 
51
 
52
  def generate_feedback(question, answer):
53
  try:
54
  question_embedding = get_embedding(question)
55
  answer_embedding = get_embedding(answer)
56
- tf.experimental.numpy.experimental_enable_numpy_behavior()
57
  # Calculate cosine similarity
58
  dot_product = np.dot(question_embedding, answer_embedding.T)
59
  norms = np.linalg.norm(question_embedding) * np.linalg.norm(answer_embedding)
@@ -219,6 +244,11 @@ def process_resume(file_obj):
219
 
220
  # Process the PDF
221
  raw_text = file_processing(file_path)
 
 
 
 
 
222
  processed_data = getallinfo(raw_text)
223
 
224
  # Clean up temporary file
@@ -245,32 +275,17 @@ def process_resume(file_obj):
245
  except Exception as e:
246
  return f"Error processing file: {str(e)}", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), None
247
 
248
- # In PrepGenie/app.py, replace the existing start_interview function with this:
249
-
250
  def start_interview(roles, processed_resume_data):
251
  """Starts the interview process."""
252
  if not roles or not processed_resume_data:
253
- # Return initial/empty states for UI components
254
- return (
255
- "Please select a role and ensure resume is processed.",
256
- "", # initial question
257
- gr.update(visible=False), # audio_input
258
- gr.update(visible=False), # submit_answer_btn
259
- gr.update(visible=False), # next_question_btn
260
- gr.update(visible=False), # submit_interview_btn
261
- gr.update(visible=False), # feedback_display
262
- gr.update(visible=False), # metrics_display
263
- gr.update(visible=False), # question_display (redundant, but matches output count)
264
- gr.update(visible=False), # answer_instructions
265
- {} # interview_state (empty dict)
266
- )
267
 
268
  try:
269
  questions = generate_questions(roles, processed_resume_data)
270
  initial_question = questions[0] if questions else "Could you please introduce yourself?"
271
 
272
  # Initialize state for the interview
273
- interview_state_data = {
274
  "questions": questions,
275
  "current_q_index": 0,
276
  "answers": [],
@@ -280,52 +295,47 @@ def start_interview(roles, processed_resume_data):
280
  "resume_data": processed_resume_data
281
  }
282
 
283
- # Return values matching the outputs list for start_interview_btn.click
284
  return (
285
  "Interview started. Please answer the first question.",
286
  initial_question,
287
- gr.update(visible=True), # audio_input visible
288
- gr.update(visible=True), # submit_answer_btn visible
289
- gr.update(visible=True), # next_question_btn visible
290
- gr.update(visible=False), # submit_interview_btn hidden initially
291
- gr.update(visible=False), # feedback_display hidden initially
292
- gr.update(visible=False), # metrics_display hidden initially
293
- gr.update(visible=True), # question_display visible
294
- gr.update(visible=True), # answer_instructions visible
295
- interview_state_data # Update the interview_state object
 
 
 
 
 
296
  )
297
  except Exception as e:
298
- error_msg = f"Error starting interview: {str(e)}"
299
- print(error_msg) # Log the error
300
- return (
301
- error_msg,
302
- "", # No question
303
- gr.update(visible=False), # Hide components
304
- gr.update(visible=False),
305
- gr.update(visible=False),
306
- gr.update(visible=False),
307
- gr.update(visible=False),
308
- gr.update(visible=False),
309
- gr.update(visible=False), # question_display
310
- gr.update(visible=False), # answer_instructions
311
- {} # Empty state on error
312
- )
313
  def submit_answer(audio, interview_state):
314
  """Handles submitting an answer via audio."""
315
  if not audio or not interview_state:
316
- return "No audio recorded or interview not started.", "", interview_state, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
317
 
318
  try:
319
  # Save audio to a temporary file
320
  temp_dir = tempfile.mkdtemp()
321
  audio_file_path = os.path.join(temp_dir, "recorded_audio.wav")
322
- audio[1].save(audio_file_path) # audio is a tuple (sample_rate, numpy_array)
 
 
 
 
323
 
324
  # Convert audio file to text
325
  r = sr.Recognizer()
326
  with sr.AudioFile(audio_file_path) as source:
327
- audio_data = r.record(source)
328
- answer_text = r.recognize_google(audio_data)
329
  print(f"Recognized Answer: {answer_text}")
330
 
331
  # Clean up temporary audio file
@@ -377,7 +387,7 @@ def submit_answer(audio, interview_state):
377
  def next_question(interview_state):
378
  """Moves to the next question or ends the interview."""
379
  if not interview_state:
380
- return "Interview not started.", "", interview_state, gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
381
 
382
  current_q_index = interview_state["current_q_index"]
383
  total_questions = len(interview_state["questions"])
@@ -431,117 +441,148 @@ def submit_interview(interview_state):
431
 
432
  return "Interview submitted successfully!", interview_state
433
 
434
- # --- Gradio Interface ---
435
 
436
- with gr.Blocks(title="PrepGenie - Mock Interview") as demo:
437
- gr.Markdown("# 🦈 PrepGenie - Mock Interview")
438
- gr.Markdown("Prepare for your next interview with AI-powered feedback.")
 
 
 
 
439
 
440
- # State to hold interview data
441
- interview_state = gr.State({})
442
 
443
- # File Upload Section
444
- with gr.Row():
445
- with gr.Column():
446
- file_upload = gr.File(label="Upload Resume (PDF)", file_types=[".pdf"])
447
- process_btn = gr.Button("Process Resume")
448
- with gr.Column():
449
- file_status = gr.Textbox(label="Status", interactive=False)
450
-
451
- # Role Selection (Initially hidden)
452
- role_selection = gr.Dropdown(
453
- choices=["Data Scientist", "Software Engineer", "Product Manager", "Data Analyst", "Business Analyst"],
454
- multiselect=True,
455
- label="Select Job Role(s)",
456
- visible=False
457
- )
458
- start_interview_btn = gr.Button("Start Interview", visible=False)
459
-
460
- # Interview Section (Initially hidden)
461
- question_display = gr.Textbox(label="Question", interactive=False, visible=False)
462
- answer_instructions = gr.Markdown("Click 'Record Answer' and speak your response.", visible=False)
463
- audio_input = gr.Audio(label="Record Answer", type="numpy", visible=False)
464
- submit_answer_btn = gr.Button("Submit Answer", visible=False)
465
- next_question_btn = gr.Button("Next Question", visible=False)
466
- submit_interview_btn = gr.Button("Submit Interview", visible=False, variant="primary")
467
-
468
- # Feedback and Metrics (Initially hidden)
469
- answer_display = gr.Textbox(label="Your Answer", interactive=False, visible=False)
470
- feedback_display = gr.Textbox(label="Feedback", interactive=False, visible=False)
471
- metrics_display = gr.JSON(label="Metrics", visible=False)
472
-
473
- # Hidden textbox to hold processed resume data temporarily
474
- processed_resume_data = gr.Textbox(visible=False)
475
-
476
- # --- Event Listeners ---
477
-
478
- process_btn.click(
479
- fn=process_resume,
480
- inputs=[file_upload],
481
- outputs=[
482
- file_status, role_selection, start_interview_btn,
483
- question_display, answer_instructions, audio_input,
484
- submit_answer_btn, next_question_btn, submit_interview_btn,
485
- answer_display, feedback_display, metrics_display,
486
- processed_resume_data # Pass processed data for next step
487
- ]
488
- )
489
-
490
- # Start Interview
491
- start_interview_btn.click(
492
- fn=start_interview,
493
- inputs=[role_selection, processed_resume_data],
494
- outputs=[
495
- file_status, # Status message
496
- question_display, # First question text
497
- audio_input, # Audio input visibility
498
- submit_answer_btn, # Submit Answer button visibility
499
- next_question_btn, # Next Question button visibility
500
- submit_interview_btn, # Submit Interview button visibility (initially hidden)
501
- feedback_display, # Feedback textbox (initially hidden/empty)
502
- metrics_display, # Metrics display (initially hidden/empty)
503
- question_display, # (Duplicate reference, likely not needed, but kept for structure)
504
- answer_instructions, # Answer instructions visibility
505
- interview_state # THE KEY CHANGE: Update the entire state object
506
- ]
507
- )
508
 
 
 
509
 
 
510
 
511
- # Submit Answer
512
- submit_answer_btn.click(
513
- fn=submit_answer,
514
- inputs=[audio_input, interview_state],
515
- outputs=[
516
- file_status, answer_display, interview_state,
517
- feedback_display, feedback_display, # Update value and visibility
518
- metrics_display, metrics_display, # Update value and visibility
519
- audio_input, submit_answer_btn, next_question_btn,
520
- submit_interview_btn, question_display, answer_instructions
521
- ]
522
- )
523
-
524
- # Next Question
525
- next_question_btn.click(
526
- fn=next_question,
527
- inputs=[interview_state],
528
- outputs=[
529
- file_status, question_display, interview_state,
530
- audio_input, submit_answer_btn, next_question_btn,
531
- feedback_display, metrics_display, submit_interview_btn,
532
- question_display, answer_instructions,
533
- answer_display, metrics_display # Clear previous answer/metrics display
534
- ]
535
- )
536
-
537
- # Submit Interview (Placeholder for evaluation trigger)
538
- submit_interview_btn.click(
539
- fn=submit_interview,
540
- inputs=[interview_state],
541
- outputs=[file_status, interview_state]
542
- # In a full app, you might navigate to an evaluation page here
543
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
 
545
  # Run the app
546
  if __name__ == "__main__":
547
- demo.launch() # You can add server_name="0.0.0.0", server_port=7860 for external access
 
4
  import tempfile
5
  import PyPDF2
6
  import google.generativeai as genai
7
+ # import tensorflow as tf # Not directly used here, but models might need it
8
  from transformers import BertTokenizer, TFBertModel
9
  import numpy as np
10
  import speech_recognition as sr
11
+ # from gtts import gTTS # Not used directly in main app logic here
12
+ # import pygame # Not used directly in main app logic here
 
13
  import time
14
  from dotenv import load_dotenv
15
 
 
17
  load_dotenv()
18
 
19
  # Configure Generative AI
20
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY") or "YOUR_DEFAULT_API_KEY_HERE") # Use environment variable or set a default
21
+ text_model = genai.GenerativeModel("gemini-pro")
22
+
23
+ # Load BERT model and tokenizer (Consider lazy loading if performance is an issue)
24
+ try:
25
+ model = TFBertModel.from_pretrained("bert-base-uncased")
26
+ tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
27
+ BERT_AVAILABLE = True
28
+ except Exception as e:
29
+ print(f"Warning: Could not load BERT model/tokenizer: {e}")
30
+ BERT_AVAILABLE = False
31
+ model = None
32
+ tokenizer = None
33
 
34
  # --- Helper Functions (Logic from Streamlit) ---
35
 
36
  def getallinfo(data):
37
+ if not data.strip():
38
+ return "No data provided."
39
  text = f"""{data} is given by the user. Make sure you are getting the details like name, experience,
40
  education, skills of the user like in a resume. If the details are not provided return: not a resume.
41
  If details are provided then please try again and format the whole in a single paragraph covering all the information. """
42
+ try:
43
+ response = text_model.generate_content(text)
44
+ response.resolve()
45
+ return response.text
46
+ except Exception as e:
47
+ print(f"Error in getallinfo: {e}")
48
+ return "Error processing resume data."
49
 
50
  def file_processing(pdf_file_path): # Takes file path now
51
+ try:
52
+ with open(pdf_file_path, "rb") as f:
53
+ reader = PyPDF2.PdfReader(f)
54
+ text = ""
55
+ for page in reader.pages:
56
+ text += page.extract_text()
57
+ return text
58
+ except Exception as e:
59
+ print(f"Error processing PDF: {e}")
60
+ return ""
61
 
62
  def get_embedding(text):
63
+ if not BERT_AVAILABLE or not model or not tokenizer:
64
+ print("BERT model not available for embedding.")
65
+ # Return a dummy embedding or handle the error appropriately
66
+ return np.zeros((1, 768)) # Dummy embedding size for bert-base-uncased
67
+
68
+ try:
69
+ # Add padding/truncation to handle variable lengths robustly
70
+ encoded_text = tokenizer(text, return_tensors="tf", truncation=True, padding=True, max_length=512)
71
+ output = model(encoded_text)
72
+ embedding = output.last_hidden_state[:, 0, :] # CLS token embedding
73
+ return embedding.numpy() # Convert to numpy for easier handling
74
+ except Exception as e:
75
+ print(f"Error getting embedding: {e}")
76
+ return np.zeros((1, 768)) # Return dummy embedding on error
77
 
78
  def generate_feedback(question, answer):
79
  try:
80
  question_embedding = get_embedding(question)
81
  answer_embedding = get_embedding(answer)
 
82
  # Calculate cosine similarity
83
  dot_product = np.dot(question_embedding, answer_embedding.T)
84
  norms = np.linalg.norm(question_embedding) * np.linalg.norm(answer_embedding)
 
244
 
245
  # Process the PDF
246
  raw_text = file_processing(file_path)
247
+ if not raw_text.strip():
248
+ os.remove(file_path)
249
+ os.rmdir(temp_dir)
250
+ return "Could not extract text from the PDF.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
251
+
252
  processed_data = getallinfo(raw_text)
253
 
254
  # Clean up temporary file
 
275
  except Exception as e:
276
  return f"Error processing file: {str(e)}", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), None
277
 
 
 
278
  def start_interview(roles, processed_resume_data):
279
  """Starts the interview process."""
280
  if not roles or not processed_resume_data:
281
+ return "Please select a role and ensure resume is processed.", "", [], [], {}, {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
  try:
284
  questions = generate_questions(roles, processed_resume_data)
285
  initial_question = questions[0] if questions else "Could you please introduce yourself?"
286
 
287
  # Initialize state for the interview
288
+ interview_state = {
289
  "questions": questions,
290
  "current_q_index": 0,
291
  "answers": [],
 
295
  "resume_data": processed_resume_data
296
  }
297
 
 
298
  return (
299
  "Interview started. Please answer the first question.",
300
  initial_question,
301
+ questions,
302
+ [], # answers
303
+ {}, # interactions
304
+ {}, # metrics (initially empty)
305
+ gr.update(visible=True), # Audio input
306
+ gr.update(visible=True), # Submit Answer button
307
+ gr.update(visible=True), # Next Question button
308
+ gr.update(visible=False), # Submit Interview button (hidden initially)
309
+ gr.update(visible=False), # Feedback textbox
310
+ gr.update(visible=False), # Metrics display
311
+ gr.update(visible=False), # Evaluation button (hidden initially)
312
+ gr.update(visible=True), # Question display
313
+ gr.update(visible=True), # Answer instructions
314
+ interview_state
315
  )
316
  except Exception as e:
317
+ return f"Error starting interview: {str(e)}", "", [], [], {}, {}, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), None
318
+
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  def submit_answer(audio, interview_state):
320
  """Handles submitting an answer via audio."""
321
  if not audio or not interview_state:
322
+ return "No audio recorded or interview not started.", "", interview_state, gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
323
 
324
  try:
325
  # Save audio to a temporary file
326
  temp_dir = tempfile.mkdtemp()
327
  audio_file_path = os.path.join(temp_dir, "recorded_audio.wav")
328
+ # audio is a tuple (sample_rate, numpy_array)
329
+ sample_rate, audio_data = audio
330
+ # Use soundfile or scipy to save the numpy array as a WAV file
331
+ import soundfile as sf
332
+ sf.write(audio_file_path, audio_data, sample_rate)
333
 
334
  # Convert audio file to text
335
  r = sr.Recognizer()
336
  with sr.AudioFile(audio_file_path) as source:
337
+ audio_data_sr = r.record(source)
338
+ answer_text = r.recognize_google(audio_data_sr)
339
  print(f"Recognized Answer: {answer_text}")
340
 
341
  # Clean up temporary audio file
 
387
  def next_question(interview_state):
388
  """Moves to the next question or ends the interview."""
389
  if not interview_state:
390
+ return "Interview not started.", "", interview_state, gr.update(visible=True), gr.update(visible=True), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
391
 
392
  current_q_index = interview_state["current_q_index"]
393
  total_questions = len(interview_state["questions"])
 
441
 
442
  return "Interview submitted successfully!", interview_state
443
 
444
+ # --- Login and Navigation Logic ---
445
 
446
+ def login(username, password):
447
+ # Simple mock login - replace with real authentication logic
448
+ # For demo, accept any non-empty username/password
449
+ if username and password:
450
+ return f"Welcome, {username}!", gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), "", ""
451
+ else:
452
+ return "Please enter username and password.", gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), username, password
453
 
454
+ def logout():
455
+ return "", gr.update(visible=False), gr.update(visible=True), gr.update(visible=True), "", ""
456
 
457
+ def navigate_to_interview():
458
+ return gr.update(visible=True), gr.update(visible=False) # Show interview, hide chat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
+ def navigate_to_chat():
461
+ return gr.update(visible=False), gr.update(visible=True) # Hide interview, show chat
462
 
463
+ # --- Gradio Interface ---
464
 
465
+ with gr.Blocks(title="PrepGenie - Mock Interview") as demo:
466
+ gr.Markdown("# 🦈 PrepGenie")
467
+ # State to hold interview data
468
+ interview_state = gr.State({})
469
+ # State for username
470
+ user_state = gr.State("")
471
+
472
+ # --- Login Section ---
473
+ with gr.Column(visible=True) as login_section:
474
+ gr.Markdown("## Login")
475
+ username_input = gr.Textbox(label="Username")
476
+ password_input = gr.Textbox(label="Password", type="password")
477
+ login_btn = gr.Button("Login")
478
+ login_status = gr.Textbox(label="Status", interactive=False)
479
+ # Initially visible login section
480
+ login_btn.click(
481
+ fn=login,
482
+ inputs=[username_input, password_input],
483
+ outputs=[login_status, login_section, interview_selection, chat_selection, username_input, password_input]
484
+ )
485
+
486
+ # --- Main App Sections (Initially Hidden) ---
487
+ with gr.Column(visible=False) as main_app:
488
+ with gr.Row():
489
+ with gr.Column(scale=1):
490
+ logout_btn = gr.Button("Logout")
491
+ with gr.Column(scale=4):
492
+ gr.Markdown(f"### Welcome, User!") # This won't dynamically update easily in Gradio Blocks without JS
493
+
494
+ with gr.Row():
495
+ with gr.Column(scale=1):
496
+ interview_btn = gr.Button("Mock Interview")
497
+ chat_btn = gr.Button("Chat with Resume")
498
+ with gr.Column(scale=4):
499
+ # --- Interview Section ---
500
+ with gr.Column(visible=False) as interview_selection:
501
+ gr.Markdown("## Mock Interview")
502
+ # File Upload Section
503
+ with gr.Row():
504
+ with gr.Column():
505
+ file_upload = gr.File(label="Upload Resume (PDF)", file_types=[".pdf"])
506
+ process_btn = gr.Button("Process Resume")
507
+ with gr.Column():
508
+ file_status = gr.Textbox(label="Status", interactive=False)
509
+
510
+ # Role Selection (Initially hidden)
511
+ role_selection = gr.Dropdown(
512
+ choices=["Data Scientist", "Software Engineer", "Product Manager", "Data Analyst", "Business Analyst"],
513
+ multiselect=True,
514
+ label="Select Job Role(s)",
515
+ visible=False
516
+ )
517
+ start_interview_btn = gr.Button("Start Interview", visible=False)
518
+
519
+ # Interview Section (Initially hidden)
520
+ question_display = gr.Textbox(label="Question", interactive=False, visible=False)
521
+ answer_instructions = gr.Markdown("Click 'Record Answer' and speak your response.", visible=False)
522
+ audio_input = gr.Audio(label="Record Answer", type="numpy", visible=False)
523
+ submit_answer_btn = gr.Button("Submit Answer", visible=False)
524
+ next_question_btn = gr.Button("Next Question", visible=False)
525
+ submit_interview_btn = gr.Button("Submit Interview", visible=False, variant="primary")
526
+
527
+ # Feedback and Metrics (Initially hidden)
528
+ answer_display = gr.Textbox(label="Your Answer", interactive=False, visible=False)
529
+ feedback_display = gr.Textbox(label="Feedback", interactive=False, visible=False)
530
+ metrics_display = gr.JSON(label="Metrics", visible=False)
531
+
532
+ # Hidden textbox to hold processed resume data temporarily
533
+ processed_resume_data = gr.Textbox(visible=False)
534
+
535
+ # --- Event Listeners for Interview ---
536
+ # Process Resume
537
+ process_btn.click(
538
+ fn=process_resume,
539
+ inputs=[file_upload],
540
+ outputs=[file_status, role_selection, start_interview_btn, question_display, answer_instructions, audio_input, submit_answer_btn, next_question_btn, submit_interview_btn, answer_display, feedback_display, metrics_display, processed_resume_data]
541
+ )
542
+
543
+ # Start Interview
544
+ start_interview_btn.click(
545
+ fn=start_interview,
546
+ inputs=[role_selection, processed_resume_data],
547
+ outputs=[file_status, question_display, interview_state["questions"], interview_state["answers"], interview_state["interactions"], interview_state["metrics_list"], audio_input, submit_answer_btn, next_question_btn, submit_interview_btn, feedback_display, metrics_display, interview_state, question_display, answer_instructions, interview_state]
548
+ )
549
+
550
+ # Submit Answer
551
+ submit_answer_btn.click(
552
+ fn=submit_answer,
553
+ inputs=[audio_input, interview_state],
554
+ outputs=[file_status, answer_display, interview_state, feedback_display, feedback_display, metrics_display, metrics_display, audio_input, submit_answer_btn, next_question_btn, submit_interview_btn, question_display, answer_instructions]
555
+ )
556
+
557
+ # Next Question
558
+ next_question_btn.click(
559
+ fn=next_question,
560
+ inputs=[interview_state],
561
+ outputs=[file_status, question_display, interview_state, audio_input, submit_answer_btn, next_question_btn, feedback_display, metrics_display, submit_interview_btn, question_display, answer_instructions, answer_display, metrics_display]
562
+ )
563
+
564
+ # Submit Interview (Placeholder for evaluation trigger)
565
+ submit_interview_btn.click(
566
+ fn=submit_interview,
567
+ inputs=[interview_state],
568
+ outputs=[file_status, interview_state]
569
+ # In a full app, you might navigate to an evaluation page here
570
+ )
571
+
572
+ # --- Chat Section ---
573
+ with gr.Column(visible=False) as chat_selection:
574
+ gr.Markdown("## Chat with Resume (Placeholder)")
575
+ gr.Markdown("This section would contain the chat interface logic from `chat.py`.")
576
+ # You would integrate the chat logic here, similar to how interview is done.
577
+ # For now, it's a placeholder.
578
+ chat_placeholder = gr.Textbox(label="Chat Placeholder", value="Chat functionality would be integrated here.", interactive=False)
579
+
580
+
581
+ # Navigation buttons
582
+ interview_btn.click(fn=navigate_to_interview, inputs=None, outputs=[interview_selection, chat_selection])
583
+ chat_btn.click(fn=navigate_to_chat, inputs=None, outputs=[interview_selection, chat_selection])
584
+ logout_btn.click(fn=logout, inputs=None, outputs=[login_status, login_section, interview_selection, chat_selection, username_input, password_input])
585
 
586
  # Run the app
587
  if __name__ == "__main__":
588
+ demo.launch(share=True) # You can add server_name="0.0.0.0", server_port=7860 for external access