muddasser commited on
Commit
1f3ee71
·
verified ·
1 Parent(s): cf3fa48

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +13 -6
app.py CHANGED
@@ -1,10 +1,8 @@
1
  """
2
  🎙️ AI Interview Agent — Reem
3
  Bilingual (English / Arabic) first-round interview assistant for HR.
4
-
5
  Every turn (questions and answers) is written to SQLite as it happens, so the
6
  full transcript is available to HR afterwards for assessment.
7
-
8
  Secrets / environment:
9
  GROQ_API_KEY required (speech-to-text + LLM)
10
  HR_PASSWORD required to open the HR portal (portal stays locked if unset)
@@ -94,6 +92,8 @@ NOTICE = {
94
  "Please do not share sensitive personal information (for example health details, religion, "
95
  "or ID numbers) in your answers.",
96
  "You may stop at any time. Contact HR to ask for access to, or deletion of, your data.",
 
 
97
  ], "I have read the above and agree to take this interview."),
98
  "ar": ("قبل أن نبدأ", [
99
  "تتم هذه المقابلة بواسطة مساعد ذكاء اصطناعي وليس بواسطة موظف.",
@@ -104,6 +104,7 @@ NOTICE = {
104
  "القرارات الوظيفية يتخذها أشخاص وليس الذكاء الاصطناعي.",
105
  "يرجى عدم ذكر معلومات شخصية حساسة (مثل التفاصيل الصحية أو الدين أو أرقام الهوية) في إجاباتكم.",
106
  "يمكنكم التوقف في أي وقت، ويمكنكم التواصل مع الموارد البشرية لطلب الاطلاع على بياناتكم أو حذفها.",
 
107
  ], "قرأتُ ما سبق وأوافق على إجراء هذه المقابلة."),
108
  }
109
 
@@ -536,13 +537,16 @@ function stop(){c.classList.remove('speaking');r.classList.remove('active');s.te
536
  a.addEventListener('ended',stop);a.addEventListener('pause',stop);
537
  function p(){var x=a.play();if(x&&x.catch)x.catch(function(){t.style.display='inline-block';s.textContent='Tap to hear';});}
538
  t.addEventListener('click',function(){a.play().catch(function(){});});
539
- if(document.readyState==='complete')p();else window.addEventListener('load',p);})();"""
 
540
 
541
 
542
- def render_avatar(img_b64: str, name: str, audio_b64: str = "") -> str:
 
543
  img = (f'<img src="data:image/jpeg;base64,{img_b64}" alt="{html.escape(name)}">'
544
  if img_b64 else '<div class="ph">👩‍💼</div>')
545
- script = AUDIO_JS.replace("__AUDIO__", audio_b64) if audio_b64 else ""
 
546
  return (AVATAR_TEMPLATE.replace("__IMG__", img)
547
  .replace("__NAME__", html.escape(name)).replace("__SCRIPT__", script))
548
 
@@ -578,6 +582,7 @@ def begin_interview(inv, pos):
578
  "questions": questions, "q_index": 0, "followups_used": 0, "done": False,
579
  "stage": "ready"}
580
  ss.pending_audio = tts_b64(greeting, lang)
 
581
  st.rerun()
582
 
583
 
@@ -592,6 +597,7 @@ def resume_interview(inv, pos, row):
592
  "questions": questions, "q_index": min(row["q_index"], len(questions) - 1),
593
  "followups_used": row["followups_used"], "done": False, "stage": stage}
594
  ss.pending_audio = tts_b64(last[-1]["content"], lang) if last else ""
 
595
  st.rerun()
596
 
597
 
@@ -723,9 +729,10 @@ def interview_screen():
723
 
724
  with col1:
725
  audio = ss.get("pending_audio", "")
 
726
  if "_photo" not in ss:
727
  ss["_photo"] = load_b64(PHOTO_PATH)
728
- st.components.v1.html(render_avatar(ss["_photo"], AGENT_NAME[lang], audio), height=480)
729
  ss.pending_audio = ""
730
 
731
  with col2:
 
1
  """
2
  🎙️ AI Interview Agent — Reem
3
  Bilingual (English / Arabic) first-round interview assistant for HR.
 
4
  Every turn (questions and answers) is written to SQLite as it happens, so the
5
  full transcript is available to HR afterwards for assessment.
 
6
  Secrets / environment:
7
  GROQ_API_KEY required (speech-to-text + LLM)
8
  HR_PASSWORD required to open the HR portal (portal stays locked if unset)
 
92
  "Please do not share sensitive personal information (for example health details, religion, "
93
  "or ID numbers) in your answers.",
94
  "You may stop at any time. Contact HR to ask for access to, or deletion of, your data.",
95
+ "Please turn your volume on. After you start, tap the 🔊 button next to the avatar once "
96
+ "to hear the first message.",
97
  ], "I have read the above and agree to take this interview."),
98
  "ar": ("قبل أن نبدأ", [
99
  "تتم هذه المقابلة بواسطة مساعد ذكاء اصطناعي وليس بواسطة موظف.",
 
104
  "القرارات الوظيفية يتخذها أشخاص وليس الذكاء الاصطناعي.",
105
  "يرجى عدم ذكر معلومات شخصية حساسة (مثل التفاصيل الصحية أو الدين أو أرقام الهوية) في إجاباتكم.",
106
  "يمكنكم التوقف في أي وقت، ويمكنكم التواصل مع الموارد البشرية لطلب الاطلاع على بياناتكم أو حذفها.",
107
+ "يرجى تشغيل الصوت. بعد البدء، اضغطوا على زر 🔊 بجانب الصورة مرة واحدة لسماع الرسالة الأولى.",
108
  ], "قرأتُ ما سبق وأوافق على إجراء هذه المقابلة."),
109
  }
110
 
 
537
  a.addEventListener('ended',stop);a.addEventListener('pause',stop);
538
  function p(){var x=a.play();if(x&&x.catch)x.catch(function(){t.style.display='inline-block';s.textContent='Tap to hear';});}
539
  t.addEventListener('click',function(){a.play().catch(function(){});});
540
+ if(__MANUAL__){t.style.display='inline-block';s.textContent='Tap to hear';}
541
+ else if(document.readyState==='complete')p();else window.addEventListener('load',p);})();"""
542
 
543
 
544
+ def render_avatar(img_b64: str, name: str, audio_b64: str = "", manual: bool = False) -> str:
545
+ """manual=True skips autoplay and shows the tap button (browsers block the first autoplay)."""
546
  img = (f'<img src="data:image/jpeg;base64,{img_b64}" alt="{html.escape(name)}">'
547
  if img_b64 else '<div class="ph">👩‍💼</div>')
548
+ script = (AUDIO_JS.replace("__AUDIO__", audio_b64)
549
+ .replace("__MANUAL__", "true" if manual else "false")) if audio_b64 else ""
550
  return (AVATAR_TEMPLATE.replace("__IMG__", img)
551
  .replace("__NAME__", html.escape(name)).replace("__SCRIPT__", script))
552
 
 
582
  "questions": questions, "q_index": 0, "followups_used": 0, "done": False,
583
  "stage": "ready"}
584
  ss.pending_audio = tts_b64(greeting, lang)
585
+ ss.manual_audio = True # first audio of the session: browsers block autoplay, show tap button
586
  st.rerun()
587
 
588
 
 
597
  "questions": questions, "q_index": min(row["q_index"], len(questions) - 1),
598
  "followups_used": row["followups_used"], "done": False, "stage": stage}
599
  ss.pending_audio = tts_b64(last[-1]["content"], lang) if last else ""
600
+ ss.manual_audio = True # fresh page load: no prior user gesture, autoplay will be blocked
601
  st.rerun()
602
 
603
 
 
729
 
730
  with col1:
731
  audio = ss.get("pending_audio", "")
732
+ manual = ss.pop("manual_audio", False)
733
  if "_photo" not in ss:
734
  ss["_photo"] = load_b64(PHOTO_PATH)
735
+ st.components.v1.html(render_avatar(ss["_photo"], AGENT_NAME[lang], audio, manual), height=480)
736
  ss.pending_audio = ""
737
 
738
  with col2: