Update app.py
Browse files
app.py
CHANGED
|
@@ -13,7 +13,12 @@ import speech_recognition as sr
|
|
| 13 |
import cv2
|
| 14 |
import numpy as np
|
| 15 |
import ast
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
# Suppress warnings
|
| 19 |
warnings.filterwarnings("ignore", category=UserWarning, module="gtts")
|
|
@@ -37,60 +42,41 @@ def extract_text_from_pdf(pdf_file):
|
|
| 37 |
return f"Error reading PDF: {str(e)}"
|
| 38 |
|
| 39 |
# Analyze resume and generate questions
|
| 40 |
-
def analyze_resume(resume_text,
|
| 41 |
generic_questions = [
|
| 42 |
"What’s your greatest strength?",
|
| 43 |
"Describe a challenge you overcame.",
|
| 44 |
"Why do you want this role?"
|
| 45 |
]
|
| 46 |
-
if not resume_text
|
| 47 |
return generic_questions[:difficulty]
|
| 48 |
|
| 49 |
questions = []
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
if skills:
|
| 56 |
-
first_skill = skills[0].split(',')[0].strip()
|
| 57 |
-
questions.append(f"Tell me about a time you used {first_skill} in a project.")
|
| 58 |
-
if experience:
|
| 59 |
-
try:
|
| 60 |
-
company_name = re.search(r"at\s+([\w\s]+?)\s*\(", experience[0]) or "the company"
|
| 61 |
-
if isinstance(company_name, str):
|
| 62 |
-
company_name = company_name
|
| 63 |
-
else:
|
| 64 |
-
company_name = company_name.group(1).strip()
|
| 65 |
-
questions.append(f"Can you describe a key contribution you made at {company_name}?")
|
| 66 |
-
except Exception:
|
| 67 |
-
pass
|
| 68 |
-
if education:
|
| 69 |
-
first_education = education[0].split('(')[0].strip()
|
| 70 |
-
questions.append(f"How did your education at {first_education} prepare you for this role?")
|
| 71 |
-
|
| 72 |
-
if custom_questions:
|
| 73 |
-
with open(custom_questions.name, "r") as f:
|
| 74 |
-
questions.extend(f.read().splitlines())
|
| 75 |
|
| 76 |
return (questions + generic_questions)[:max(1, difficulty)]
|
| 77 |
|
| 78 |
-
#
|
| 79 |
def provide_feedback(response):
|
| 80 |
if not response:
|
| 81 |
return "Please provide an answer."
|
| 82 |
-
word_count = len(response.split())
|
| 83 |
sentiment = sentiment_analyzer(response)[0]
|
| 84 |
feedback = []
|
| 85 |
-
|
|
|
|
| 86 |
feedback.append("Your answer is short. Please elaborate.")
|
| 87 |
if "I don’t know" in response.lower():
|
| 88 |
feedback.append("Try sharing a related experience instead.")
|
| 89 |
if sentiment["label"] == "NEGATIVE":
|
| 90 |
feedback.append("Try to sound more positive and confident!")
|
| 91 |
-
return " ".join(feedback) or "Great answer! Well detailed and positive."
|
| 92 |
|
| 93 |
-
|
|
|
|
|
|
|
| 94 |
def analyze_code(code):
|
| 95 |
if not code:
|
| 96 |
return "No code provided."
|
|
@@ -100,36 +86,11 @@ def analyze_code(code):
|
|
| 100 |
except SyntaxError as e:
|
| 101 |
return f"Code error: {str(e)}"
|
| 102 |
|
| 103 |
-
# Create interview video
|
| 104 |
-
def create_interview_video(questions, responses, output_path="interview_simulation.mp4"):
|
| 105 |
-
try:
|
| 106 |
-
frame_rate = 1
|
| 107 |
-
resolution = (1280, 720)
|
| 108 |
-
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 109 |
-
out = cv2.VideoWriter(output_path, fourcc, frame_rate, resolution)
|
| 110 |
-
|
| 111 |
-
for i, (question, response) in enumerate(zip(questions, responses)):
|
| 112 |
-
frame = np.zeros((resolution[1], resolution[0], 3), dtype=np.uint8)
|
| 113 |
-
cv2.putText(frame, f"Question {i+1}: {question}", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
|
| 114 |
-
for _ in range(5 * frame_rate):
|
| 115 |
-
out.write(frame)
|
| 116 |
-
|
| 117 |
-
if response:
|
| 118 |
-
frame = np.zeros((resolution[1], resolution[0], 3), dtype=np.uint8)
|
| 119 |
-
cv2.putText(frame, f"Response: {response}", (50, 200), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)
|
| 120 |
-
for _ in range(5 * frame_rate):
|
| 121 |
-
out.write(frame)
|
| 122 |
-
|
| 123 |
-
out.release()
|
| 124 |
-
return output_path
|
| 125 |
-
except Exception as e:
|
| 126 |
-
return f"Video creation failed: {str(e)}"
|
| 127 |
-
|
| 128 |
# Transcribe audio from video or audio file
|
| 129 |
def transcribe_audio(file_path):
|
| 130 |
try:
|
| 131 |
if file_path.endswith(".mp4"): # Handle video input
|
| 132 |
-
video = VideoFileClip(file_path)
|
| 133 |
audio_path = tempfile.NamedTemporaryFile(suffix=".wav").name
|
| 134 |
video.audio.write_audiofile(audio_path)
|
| 135 |
else:
|
|
@@ -141,54 +102,10 @@ def transcribe_audio(file_path):
|
|
| 141 |
except Exception as e:
|
| 142 |
return f"Error transcribing: {str(e)}"
|
| 143 |
|
| 144 |
-
# Main interview function
|
| 145 |
-
def run_interview(pdf_file, video_file, code_input, mc_input, user_response, question_index, questions_state, responses_state, timer_state, custom_questions, difficulty):
|
| 146 |
-
try:
|
| 147 |
-
# Initialize questions if not set
|
| 148 |
-
if not questions_state:
|
| 149 |
-
resume_text = extract_text_from_pdf(pdf_file) if pdf_file else ""
|
| 150 |
-
questions_state = analyze_resume(resume_text, custom_questions, difficulty)
|
| 151 |
-
responses_state = [""] * len(questions_state)
|
| 152 |
-
timer_state = 60 # Reset timer
|
| 153 |
-
|
| 154 |
-
# Process video/audio input
|
| 155 |
-
if video_file:
|
| 156 |
-
user_response = transcribe_audio(video_file)
|
| 157 |
-
|
| 158 |
-
# Handle multiple-choice or code input if provided
|
| 159 |
-
if mc_input:
|
| 160 |
-
user_response = f"Selected: {mc_input}"
|
| 161 |
-
elif code_input:
|
| 162 |
-
user_response = code_input
|
| 163 |
-
code_feedback = analyze_code(code_input)
|
| 164 |
-
else:
|
| 165 |
-
code_feedback = ""
|
| 166 |
-
|
| 167 |
-
# Save response
|
| 168 |
-
if user_response and 0 <= question_index < len(questions_state):
|
| 169 |
-
responses_state[question_index] = user_response
|
| 170 |
-
|
| 171 |
-
# Check if interview is complete
|
| 172 |
-
if question_index >= len(questions_state):
|
| 173 |
-
video_path = create_interview_video(questions_state, responses_state)
|
| 174 |
-
return "Interview complete!", "Thank you!", video_path, questions_state, responses_state, question_index, 0, None
|
| 175 |
-
|
| 176 |
-
# Current question and feedback
|
| 177 |
-
current_question = questions_state[question_index]
|
| 178 |
-
feedback = provide_feedback(user_response) + (f" {code_feedback}" if code_feedback else "")
|
| 179 |
-
|
| 180 |
-
# Update timer (simplified for demo)
|
| 181 |
-
timer_state = max(0, timer_state - 10) # Decrement by 10 seconds per submission
|
| 182 |
-
|
| 183 |
-
return current_question, feedback, None, questions_state, responses_state, question_index + 1, timer_state, str(timer_state)
|
| 184 |
-
|
| 185 |
-
except Exception as e:
|
| 186 |
-
return f"Error: {str(e)}", "Something went wrong.", None, [], [], 0, 60, "60"
|
| 187 |
-
|
| 188 |
# Gradio interface
|
| 189 |
with gr.Blocks(title="Nancy AI - Advanced Interview Simulator") as demo:
|
| 190 |
-
gr.Markdown("# Nancy AI - Advanced Interview Simulator")
|
| 191 |
-
gr.Markdown("Upload your resume and a video response
|
| 192 |
|
| 193 |
question_state = gr.State(value=0)
|
| 194 |
questions_state = gr.State(value=[])
|
|
@@ -196,30 +113,58 @@ with gr.Blocks(title="Nancy AI - Advanced Interview Simulator") as demo:
|
|
| 196 |
timer_state = gr.State(value=60)
|
| 197 |
|
| 198 |
with gr.Row():
|
| 199 |
-
pdf_input = gr.File(label="Upload PDF Resume", file_types=[".pdf"])
|
| 200 |
-
|
| 201 |
-
difficulty = gr.Slider(1, 5, step=1, label="Difficulty Level", value=1)
|
| 202 |
|
| 203 |
with gr.Row():
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
mc_input = gr.Radio(["Option A", "Option B", "Option C"], label="Multiple Choice (if applicable)")
|
| 208 |
-
text_input = gr.Textbox(label="Your Response (Optional)", placeholder="Type your answer here...")
|
| 209 |
|
| 210 |
with gr.Row():
|
| 211 |
-
question_output = gr.Textbox(label="Current Question", interactive=False)
|
| 212 |
-
feedback_output = gr.Textbox(label="Feedback", interactive=False)
|
| 213 |
-
timer_display = gr.Textbox(label="Time Left (seconds)", interactive=False, value="60")
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 218 |
|
| 219 |
submit_btn.click(
|
| 220 |
-
fn=
|
| 221 |
-
inputs=[pdf_input, video_input, code_input,
|
| 222 |
-
outputs=[question_output, feedback_output,
|
| 223 |
)
|
| 224 |
|
| 225 |
-
demo.launch()
|
|
|
|
| 13 |
import cv2
|
| 14 |
import numpy as np
|
| 15 |
import ast
|
| 16 |
+
|
| 17 |
+
# Fixed moviepy import
|
| 18 |
+
try:
|
| 19 |
+
import moviepy.editor as mp
|
| 20 |
+
except ModuleNotFoundError:
|
| 21 |
+
raise ImportError("The 'moviepy' module is not installed. Please add 'moviepy' to your requirements.txt and restart your Hugging Face Space.")
|
| 22 |
|
| 23 |
# Suppress warnings
|
| 24 |
warnings.filterwarnings("ignore", category=UserWarning, module="gtts")
|
|
|
|
| 42 |
return f"Error reading PDF: {str(e)}"
|
| 43 |
|
| 44 |
# Analyze resume and generate questions
|
| 45 |
+
def analyze_resume(resume_text, difficulty=1):
|
| 46 |
generic_questions = [
|
| 47 |
"What’s your greatest strength?",
|
| 48 |
"Describe a challenge you overcame.",
|
| 49 |
"Why do you want this role?"
|
| 50 |
]
|
| 51 |
+
if not resume_text:
|
| 52 |
return generic_questions[:difficulty]
|
| 53 |
|
| 54 |
questions = []
|
| 55 |
+
skills = re.findall(r"Skills:\s*(.*?)(?:\n|$)", resume_text, re.DOTALL | re.IGNORECASE)
|
| 56 |
+
|
| 57 |
+
if skills:
|
| 58 |
+
first_skill = skills[0].split(',')[0].strip()
|
| 59 |
+
questions.append(f"Tell me about a time you used {first_skill} in a project.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
return (questions + generic_questions)[:max(1, difficulty)]
|
| 62 |
|
| 63 |
+
# Analyze user response with sentiment analysis
|
| 64 |
def provide_feedback(response):
|
| 65 |
if not response:
|
| 66 |
return "Please provide an answer."
|
|
|
|
| 67 |
sentiment = sentiment_analyzer(response)[0]
|
| 68 |
feedback = []
|
| 69 |
+
|
| 70 |
+
if len(response.split()) < 20:
|
| 71 |
feedback.append("Your answer is short. Please elaborate.")
|
| 72 |
if "I don’t know" in response.lower():
|
| 73 |
feedback.append("Try sharing a related experience instead.")
|
| 74 |
if sentiment["label"] == "NEGATIVE":
|
| 75 |
feedback.append("Try to sound more positive and confident!")
|
|
|
|
| 76 |
|
| 77 |
+
return " ".join(feedback) or "Great answer!"
|
| 78 |
+
|
| 79 |
+
# Analyze code syntax
|
| 80 |
def analyze_code(code):
|
| 81 |
if not code:
|
| 82 |
return "No code provided."
|
|
|
|
| 86 |
except SyntaxError as e:
|
| 87 |
return f"Code error: {str(e)}"
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
# Transcribe audio from video or audio file
|
| 90 |
def transcribe_audio(file_path):
|
| 91 |
try:
|
| 92 |
if file_path.endswith(".mp4"): # Handle video input
|
| 93 |
+
video = mp.VideoFileClip(file_path)
|
| 94 |
audio_path = tempfile.NamedTemporaryFile(suffix=".wav").name
|
| 95 |
video.audio.write_audiofile(audio_path)
|
| 96 |
else:
|
|
|
|
| 102 |
except Exception as e:
|
| 103 |
return f"Error transcribing: {str(e)}"
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
# Gradio interface
|
| 106 |
with gr.Blocks(title="Nancy AI - Advanced Interview Simulator") as demo:
|
| 107 |
+
gr.Markdown("# 🎤 Nancy AI - Advanced Interview Simulator")
|
| 108 |
+
gr.Markdown("Upload your resume and a video response to get interview questions and feedback.")
|
| 109 |
|
| 110 |
question_state = gr.State(value=0)
|
| 111 |
questions_state = gr.State(value=[])
|
|
|
|
| 113 |
timer_state = gr.State(value=60)
|
| 114 |
|
| 115 |
with gr.Row():
|
| 116 |
+
pdf_input = gr.File(label="📄 Upload PDF Resume", file_types=[".pdf"])
|
| 117 |
+
difficulty = gr.Slider(1, 5, step=1, label="🔹 Difficulty Level", value=1)
|
|
|
|
| 118 |
|
| 119 |
with gr.Row():
|
| 120 |
+
video_input = gr.Video(label="🎥 Upload or Record Video Response", interactive=True)
|
| 121 |
+
code_input = gr.Code(language="python", label="📝 Write Your Code (if applicable)")
|
| 122 |
+
text_input = gr.Textbox(label="🗣️ Your Response", placeholder="Type your answer here...")
|
|
|
|
|
|
|
| 123 |
|
| 124 |
with gr.Row():
|
| 125 |
+
question_output = gr.Textbox(label="❓ Current Question", interactive=False)
|
| 126 |
+
feedback_output = gr.Textbox(label="💡 Feedback", interactive=False)
|
| 127 |
+
timer_display = gr.Textbox(label="⏳ Time Left (seconds)", interactive=False, value="60")
|
| 128 |
+
|
| 129 |
+
submit_btn = gr.Button("✅ Submit Response")
|
| 130 |
+
|
| 131 |
+
# Define click action
|
| 132 |
+
def process_response(pdf_file, video_file, code_input, text_input, question_index, questions_state, responses_state, timer_state, difficulty):
|
| 133 |
+
try:
|
| 134 |
+
# Load or generate interview questions
|
| 135 |
+
if not questions_state:
|
| 136 |
+
resume_text = extract_text_from_pdf(pdf_file) if pdf_file else ""
|
| 137 |
+
questions_state = analyze_resume(resume_text, difficulty)
|
| 138 |
+
responses_state = [""] * len(questions_state)
|
| 139 |
+
timer_state = 60 # Reset timer
|
| 140 |
+
|
| 141 |
+
# Transcribe video/audio response
|
| 142 |
+
user_response = transcribe_audio(video_file) if video_file else text_input
|
| 143 |
+
if code_input:
|
| 144 |
+
user_response = code_input
|
| 145 |
+
code_feedback = analyze_code(code_input)
|
| 146 |
+
else:
|
| 147 |
+
code_feedback = ""
|
| 148 |
+
|
| 149 |
+
# Save response
|
| 150 |
+
if user_response and 0 <= question_index < len(questions_state):
|
| 151 |
+
responses_state[question_index] = user_response
|
| 152 |
+
|
| 153 |
+
# Provide feedback
|
| 154 |
+
feedback = provide_feedback(user_response) + (f" {code_feedback}" if code_feedback else "")
|
| 155 |
+
|
| 156 |
+
# Move to the next question
|
| 157 |
+
if question_index >= len(questions_state) - 1:
|
| 158 |
+
return "Interview complete!", "Thank you!", questions_state, responses_state, 0, None
|
| 159 |
+
|
| 160 |
+
return questions_state[question_index], feedback, questions_state, responses_state, question_index + 1, str(max(0, timer_state - 10))
|
| 161 |
+
except Exception as e:
|
| 162 |
+
return f"Error: {str(e)}", "Something went wrong.", [], [], 0, "60"
|
| 163 |
|
| 164 |
submit_btn.click(
|
| 165 |
+
fn=process_response,
|
| 166 |
+
inputs=[pdf_input, video_input, code_input, text_input, question_state, questions_state, responses_state, timer_state, difficulty],
|
| 167 |
+
outputs=[question_output, feedback_output, questions_state, responses_state, question_state, timer_display]
|
| 168 |
)
|
| 169 |
|
| 170 |
+
demo.launch()
|