barbara-multimodal commited on
Commit
196b3e3
·
1 Parent(s): 47c6abc

feat: Add job description option and skills evaluation

Browse files
Files changed (2) hide show
  1. app.py +39 -16
  2. src/utils.py +69 -4
app.py CHANGED
@@ -4,7 +4,7 @@ import gradio as gr
4
  from gradio import ChatMessage
5
 
6
  from src.constants import FULL_QUESTIONS, DEFAULT_GRADING_SYSTEM_DF, CUSTOM_CSS
7
- from src.utils import generate_session_id, highlight_feedback, show_popup, reset_popup
8
  from src.api_calls import chatbot_api_call, feedback_api_call, ideal_answer_api_call, conversation_feedback_api_call
9
 
10
  session_id = generate_session_id()
@@ -116,6 +116,7 @@ def reset_interface(conversation_mode):
116
  grading_system_df = gr.update(value=DEFAULT_GRADING_SYSTEM_DF, label="Insights")
117
  include_company_name = gr.update(interactive=True, value=False)
118
  include_resume_text = gr.update(interactive=True, value=False)
 
119
 
120
  history = [
121
  ChatMessage(
@@ -125,7 +126,7 @@ def reset_interface(conversation_mode):
125
  ChatMessage(
126
  role="assistant",
127
  content=chatbot_greeting_message,
128
- ),
129
  ]
130
 
131
  return (
@@ -138,7 +139,8 @@ def reset_interface(conversation_mode):
138
  feedback_type_state,
139
  grading_system_df,
140
  include_company_name,
141
- include_resume_text
 
142
  )
143
 
144
  # Gradio interface
@@ -148,12 +150,18 @@ def create_demo():
148
 
149
  gr.Markdown("""### Please select a conversation mode to begin""")
150
 
151
- with gr.Row():
152
  with gr.Column(scale=6):
153
- conversation_mode = gr.Radio(choices=["Interviewer", "Coach"], label="""Choose "Interviewer" to simulate a real interview or "Coach" for guidance and feedback""", value=None)
 
 
 
 
 
154
 
155
- with gr.Column(scale=2):
156
  include_company_name = gr.Checkbox(label="Include the company name in the request", value=False, interactive=False)
 
157
  include_resume_text = gr.Checkbox(label="Include the candidate's resume in the request", value=False, interactive=False)
158
 
159
  conversation_turns_limit = gr.Slider(minimum=1, maximum=20, step=1, label="Choose the number of exchanges (turns) between you and the AI agent (1 min, 20 max)", value=5, interactive=False)
@@ -196,7 +204,7 @@ def create_demo():
196
 
197
  question_dropdown.change(fn=handle_question_change, inputs=[chatbot, question_dropdown, conversation_mode], outputs=[chatbot, send_btn, msg, chatbot, feedback_box, grading_system_df])
198
 
199
- def respond(message, history, conversation_mode, selected_question, conversation_turns_limit, feedback_type, include_company_name, include_resume_text):
200
  global session_id, current_turns, interview_data_with_feedback
201
 
202
  feedback_value = None
@@ -230,14 +238,13 @@ def create_demo():
230
 
231
  if conversation_end_flag:
232
  if conversation_mode == 'Interviewer':
233
- feedback_output = conversation_feedback_api_call(chat_memory['messages'], feedback_type.lower(), include_resume_text, include_job_description="")
234
-
235
- print(feedback_output)
236
 
237
  feedback_show_legend, highlighted_feedback = highlight_feedback(feedback_output)
238
  feedback_value = [("Whole conversation feedback\n\n", None)] + highlighted_feedback
239
 
240
  criteria_feedback_data = feedback_output["criteria_feedback"]
 
241
 
242
  if criteria_feedback_data:
243
  criteria_feedback_df = pd.DataFrame(criteria_feedback_data)
@@ -247,6 +254,15 @@ def create_demo():
247
  "evaluation": "Evaluation"
248
  }, inplace=True)
249
 
 
 
 
 
 
 
 
 
 
250
  print()
251
  print(feedback_output)
252
  print()
@@ -299,11 +315,13 @@ def create_demo():
299
  ideal_answer = ideal_answer_api_call(interview_data_with_feedback, feedback_type.lower(), include_resume_text)
300
  cleaned_ideal_answer = ideal_answer.replace("\\n", " ")
301
 
 
 
302
  print(cleaned_ideal_answer)
303
 
304
  history.append(ChatMessage(
305
  role="assistant",
306
- content=cleaned_ideal_answer,
307
  metadata={"title": "💡 Ideal answer"}
308
  ))
309
 
@@ -319,20 +337,25 @@ def create_demo():
319
  gr.update(value=criteria_feedback_df, label="Insights")
320
  )
321
 
322
- conversation_mode.change(fn=reset_interface, inputs=conversation_mode, outputs=[chatbot, question_dropdown, msg, send_btn, conversation_turns_limit, feedback_box, feedback_type_dropdown, grading_system_df, include_company_name, include_resume_text])
323
 
324
- msg.submit(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns_limit, feedback_type_dropdown, include_company_name, include_resume_text], outputs=[chatbot, msg, send_btn, msg, chatbot, feedback_box, grading_system_df])
325
- send_btn.click(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns_limit, feedback_type_dropdown, include_company_name, include_resume_text], outputs=[chatbot, msg, send_btn, msg, chatbot, feedback_box, grading_system_df])
326
 
327
  popup = gr.HTML(label="Popup", elem_classes=["popup"])
328
 
329
  include_company_name.change(
330
- fn=lambda selected: show_popup(False, selected) if selected else reset_popup(),
331
  inputs=include_company_name,
332
  outputs=popup
333
  )
 
 
 
 
 
334
  include_resume_text.change(
335
- fn=lambda selected: show_popup(selected, False) if selected else reset_popup(),
336
  inputs=include_resume_text,
337
  outputs=popup
338
  )
 
4
  from gradio import ChatMessage
5
 
6
  from src.constants import FULL_QUESTIONS, DEFAULT_GRADING_SYSTEM_DF, CUSTOM_CSS
7
+ from src.utils import generate_session_id, highlight_feedback, show_popup, reset_popup, generate_skills_evaluation_markdown
8
  from src.api_calls import chatbot_api_call, feedback_api_call, ideal_answer_api_call, conversation_feedback_api_call
9
 
10
  session_id = generate_session_id()
 
116
  grading_system_df = gr.update(value=DEFAULT_GRADING_SYSTEM_DF, label="Insights")
117
  include_company_name = gr.update(interactive=True, value=False)
118
  include_resume_text = gr.update(interactive=True, value=False)
119
+ include_job_description = gr.update(interactive=True, value=True)
120
 
121
  history = [
122
  ChatMessage(
 
126
  ChatMessage(
127
  role="assistant",
128
  content=chatbot_greeting_message,
129
+ )
130
  ]
131
 
132
  return (
 
139
  feedback_type_state,
140
  grading_system_df,
141
  include_company_name,
142
+ include_resume_text,
143
+ include_job_description
144
  )
145
 
146
  # Gradio interface
 
150
 
151
  gr.Markdown("""### Please select a conversation mode to begin""")
152
 
153
+ with gr.Row(equal_height=True):
154
  with gr.Column(scale=6):
155
+ conversation_mode = gr.Radio(
156
+ choices=["Interviewer", "Coach"],
157
+ label="""Choose "Interviewer" to simulate a real interview or "Coach" for guidance and feedback""",
158
+ info=""">**Important note:**\n>This has been *hardcoded* for a **Senior Product Manager** role""",
159
+ value=None
160
+ )
161
 
162
+ with gr.Column(scale=2, variant="panel"):
163
  include_company_name = gr.Checkbox(label="Include the company name in the request", value=False, interactive=False)
164
+ include_job_description = gr.Checkbox(label="Include the job description in the request", value=True, interactive=False)
165
  include_resume_text = gr.Checkbox(label="Include the candidate's resume in the request", value=False, interactive=False)
166
 
167
  conversation_turns_limit = gr.Slider(minimum=1, maximum=20, step=1, label="Choose the number of exchanges (turns) between you and the AI agent (1 min, 20 max)", value=5, interactive=False)
 
204
 
205
  question_dropdown.change(fn=handle_question_change, inputs=[chatbot, question_dropdown, conversation_mode], outputs=[chatbot, send_btn, msg, chatbot, feedback_box, grading_system_df])
206
 
207
+ def respond(message, history, conversation_mode, selected_question, conversation_turns_limit, feedback_type, include_company_name, include_resume_text, include_job_description):
208
  global session_id, current_turns, interview_data_with_feedback
209
 
210
  feedback_value = None
 
238
 
239
  if conversation_end_flag:
240
  if conversation_mode == 'Interviewer':
241
+ feedback_output = conversation_feedback_api_call(chat_memory['messages'], feedback_type.lower(), include_resume_text, include_job_description)
 
 
242
 
243
  feedback_show_legend, highlighted_feedback = highlight_feedback(feedback_output)
244
  feedback_value = [("Whole conversation feedback\n\n", None)] + highlighted_feedback
245
 
246
  criteria_feedback_data = feedback_output["criteria_feedback"]
247
+ skills_evaluation_data = feedback_output["skills_evaluation"]
248
 
249
  if criteria_feedback_data:
250
  criteria_feedback_df = pd.DataFrame(criteria_feedback_data)
 
254
  "evaluation": "Evaluation"
255
  }, inplace=True)
256
 
257
+ if skills_evaluation_data:
258
+ skills_evaluation = generate_skills_evaluation_markdown(skills_evaluation_data)
259
+
260
+ history.append(ChatMessage(
261
+ role="assistant",
262
+ content=skills_evaluation,
263
+ metadata={"title": "🛠️ Skills evaluation"}
264
+ ))
265
+
266
  print()
267
  print(feedback_output)
268
  print()
 
315
  ideal_answer = ideal_answer_api_call(interview_data_with_feedback, feedback_type.lower(), include_resume_text)
316
  cleaned_ideal_answer = ideal_answer.replace("\\n", " ")
317
 
318
+ cleaned_ideal_answer_html = f"""<p style="text-align: left; font-size: 14px;">{cleaned_ideal_answer}</p>"""
319
+
320
  print(cleaned_ideal_answer)
321
 
322
  history.append(ChatMessage(
323
  role="assistant",
324
+ content=cleaned_ideal_answer_html,
325
  metadata={"title": "💡 Ideal answer"}
326
  ))
327
 
 
337
  gr.update(value=criteria_feedback_df, label="Insights")
338
  )
339
 
340
+ conversation_mode.change(fn=reset_interface, inputs=conversation_mode, outputs=[chatbot, question_dropdown, msg, send_btn, conversation_turns_limit, feedback_box, feedback_type_dropdown, grading_system_df, include_company_name, include_resume_text, include_job_description])
341
 
342
+ msg.submit(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns_limit, feedback_type_dropdown, include_company_name, include_resume_text, include_job_description], outputs=[chatbot, msg, send_btn, msg, chatbot, feedback_box, grading_system_df])
343
+ send_btn.click(fn=respond, inputs=[msg, chatbot, conversation_mode, question_dropdown, conversation_turns_limit, feedback_type_dropdown, include_company_name, include_resume_text, include_job_description], outputs=[chatbot, msg, send_btn, msg, chatbot, feedback_box, grading_system_df])
344
 
345
  popup = gr.HTML(label="Popup", elem_classes=["popup"])
346
 
347
  include_company_name.change(
348
+ fn=lambda selected: show_popup(False, selected, False) if selected else reset_popup(),
349
  inputs=include_company_name,
350
  outputs=popup
351
  )
352
+ include_job_description.change(
353
+ fn=lambda selected: show_popup(False, False, selected) if selected else reset_popup(),
354
+ inputs=include_job_description,
355
+ outputs=popup
356
+ )
357
  include_resume_text.change(
358
+ fn=lambda selected: show_popup(selected, False, False) if selected else reset_popup(),
359
  inputs=include_resume_text,
360
  outputs=popup
361
  )
src/utils.py CHANGED
@@ -37,14 +37,14 @@ def highlight_feedback(feedback_output):
37
  return False, [(str(feedback_output["feedback_text"]), None)]
38
  return True, [(item["text"], item["category"]) for item in feedback_output["feedback_by_category"]]
39
 
40
- def show_popup(selected_resume, selected_company):
41
  if selected_resume:
42
  return """<div id="popup-resume" style="position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%);
43
  background-color: #fffaf0; border: 2px solid #ffa500; padding: 20px;
44
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); border-radius: 10px; z-index: 1000;
45
  color: #ff6700; font-family: Arial, sans-serif; text-align: center;">
46
  <p><strong>Notice:</strong> You selected the option to include the candidate's resume.</p>
47
- <p><a href='https://huggingface.co/spaces/multimodalai/talent-interview-prep/blob/main/resources/Senior_Product_Manager_Resume.txt'
48
  style='color: #ff6700; text-decoration: none; font-weight: bold;' target="_blank">
49
  Click here to view the resume</a></p>
50
  <button onclick="document.getElementById('popup-resume').remove();"
@@ -54,7 +54,7 @@ def show_popup(selected_resume, selected_company):
54
  </button>
55
  </div>"""
56
  elif selected_company:
57
- return f"""<div id="popup-company" style="position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%);
58
  background-color: #fffaf0; border: 2px solid #ffa500; padding: 20px;
59
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); border-radius: 10px; z-index: 1000;
60
  color: #ff6700; font-family: Arial, sans-serif; text-align: center;">
@@ -66,7 +66,72 @@ def show_popup(selected_resume, selected_company):
66
  Close
67
  </button>
68
  </div>"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  return ""
70
 
71
  def reset_popup():
72
- return ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  return False, [(str(feedback_output["feedback_text"]), None)]
38
  return True, [(item["text"], item["category"]) for item in feedback_output["feedback_by_category"]]
39
 
40
+ def show_popup(selected_resume, selected_company, selected_job_description):
41
  if selected_resume:
42
  return """<div id="popup-resume" style="position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%);
43
  background-color: #fffaf0; border: 2px solid #ffa500; padding: 20px;
44
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); border-radius: 10px; z-index: 1000;
45
  color: #ff6700; font-family: Arial, sans-serif; text-align: center;">
46
  <p><strong>Notice:</strong> You selected the option to include the candidate's resume.</p>
47
+ <p><a href='https://huggingface.co/spaces/multimodalai/talent-interview-prep/resolve/main/resources/Senior_Product_Manager_Resume.txt'
48
  style='color: #ff6700; text-decoration: none; font-weight: bold;' target="_blank">
49
  Click here to view the resume</a></p>
50
  <button onclick="document.getElementById('popup-resume').remove();"
 
54
  </button>
55
  </div>"""
56
  elif selected_company:
57
+ return """<div id="popup-company" style="position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%);
58
  background-color: #fffaf0; border: 2px solid #ffa500; padding: 20px;
59
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); border-radius: 10px; z-index: 1000;
60
  color: #ff6700; font-family: Arial, sans-serif; text-align: center;">
 
66
  Close
67
  </button>
68
  </div>"""
69
+ elif selected_job_description:
70
+ return """<div id="popup-job-desc" style="position: fixed; top: 30%; left: 50%; transform: translate(-50%, -50%);
71
+ background-color: #fffaf0; border: 2px solid #ffa500; padding: 20px;
72
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); border-radius: 10px; z-index: 1000;
73
+ color: #ff6700; font-family: Arial, sans-serif; text-align: center;">
74
+ <p><strong>Notice:</strong> You selected the option to include the job description.</p>
75
+ <p><a href='https://huggingface.co/spaces/multimodalai/talent-interview-prep/resolve/main/resources/Senior_Product_Manager_Job_Description.txt'
76
+ style='color: #ff6700; text-decoration: none; font-weight: bold;' target="_blank">
77
+ Click here to view the job description</a></p>
78
+ <button onclick="document.getElementById('popup-job-desc').remove();"
79
+ style="background-color: #ff6700; color: white; border: none;
80
+ padding: 5px 10px; border-radius: 5px; cursor: pointer;">
81
+ Close
82
+ </button>
83
+ </div>"""
84
  return ""
85
 
86
  def reset_popup():
87
+ return ""
88
+
89
+ def generate_skills_evaluation_markdown(skills_data):
90
+ job_skills = skills_data["job_skills"]
91
+ candidate_skills = skills_data["candidate_skills"]["proven_skills"]
92
+ unproven_skills = skills_data["candidate_skills"]["mentioned_but_unproven_skills"]
93
+ missing_skills = skills_data["candidate_skills"]["missing_skills"]
94
+
95
+ total_job_skills = len(job_skills)
96
+ proven_skills_count = len(candidate_skills)
97
+ unproven_skills_count = len(unproven_skills)
98
+ missing_skills_count = len(missing_skills)
99
+
100
+ html = "<div style='text-align: left; font-size: 20px;'>"
101
+ html += (
102
+ f"<p>The job requires <strong>{total_job_skills} skills</strong>, and the candidate has <strong>{proven_skills_count} proven skills</strong>. "
103
+ f"There are <strong>{unproven_skills_count} unproven skills</strong> and <strong>{missing_skills_count} missing skills</strong>.</p><br>"
104
+ )
105
+ html += "<ul style='font-size: 20px;'>"
106
+
107
+ if total_job_skills > 0:
108
+ html += f"<strong>Job skills ({total_job_skills}):</strong><ol style='font-size: 18px;'>"
109
+ for skill in job_skills:
110
+ html += f"<li> <strong>{skill['name']}</strong> - {skill['description']}</li>"
111
+ html += "</ol>"
112
+
113
+ if proven_skills_count > 0:
114
+ html += f"<strong>Candidate skills ({proven_skills_count} out of {total_job_skills}):</strong><ol style='font-size: 18px;'>"
115
+ for skill in candidate_skills:
116
+ html += (f"<li><strong>{skill['name']}</strong><br>"
117
+ f" <em>Example:</em> {skill['example']}<br>"
118
+ f" <em>Metrics:</em> {skill['metrics']}</li>")
119
+ html += "</ol>"
120
+
121
+ if unproven_skills_count > 0:
122
+ html += "<strong>Mentioned but unproven skills:</strong>"
123
+ html += "<ol style='font-size: 18px;'>"
124
+ for skill in unproven_skills:
125
+ html += f"<li><strong>{skill['name']}</strong> - {skill['reason']}</li>"
126
+ html += "</ol>"
127
+
128
+ if missing_skills_count > 0:
129
+ html += "<strong>Missing skills:</strong>"
130
+ html += "<ol style='font-size: 18px;'>"
131
+ for skill in missing_skills:
132
+ html += f"<li><strong>{skill['name']}</strong> - {skill['reason']}</li>"
133
+ html += "</ol>"
134
+
135
+ html += "</ul></div>"
136
+
137
+ return html