owenkaplinsky commited on
Commit
71b19a4
Β·
verified Β·
1 Parent(s): f8a08ec

Update src/frontend/gradio/app.py

Browse files
Files changed (1) hide show
  1. src/frontend/gradio/app.py +136 -14
src/frontend/gradio/app.py CHANGED
@@ -9,6 +9,7 @@ from typing import Optional, Tuple
9
  import sys
10
  from pathlib import Path
11
  import requests
 
12
 
13
  project_root = Path(__file__).resolve().parent.parent.parent.parent
14
  sys.path.insert(0, str(project_root))
@@ -61,6 +62,41 @@ def get_voice_screening_url() -> str:
61
  voice_url = os.getenv("VOICE_SCREENING_UI_URL", "http://localhost:8502")
62
  return voice_url
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  # ============================================================================
65
  # CANDIDATE APPLICATION PORTAL
66
  # ============================================================================
@@ -831,7 +867,8 @@ def create_app():
831
  submit_btn.click(
832
  fn=submit_application,
833
  inputs=[full_name, email, phone, cv_file],
834
- outputs=application_output
 
835
  )
836
 
837
  gr.Markdown("---")
@@ -864,18 +901,10 @@ def create_app():
864
 
865
  gr.Markdown("---")
866
  gr.Markdown("## πŸŽ™οΈ Voice Screening")
867
-
868
- # Voice screening page link
869
- voice_screening_url = get_voice_screening_url()
870
- gr.HTML(f"""
871
- <div style="background: #eff6ff; border-left: 4px solid #2563eb; padding: 1rem; border-radius: 0 8px 8px 0; margin-bottom: 1rem;">
872
- <strong>πŸŽ™οΈ Voice Screening Interface:</strong><br>
873
- <a href="{voice_screening_url}" target="_blank" style="color: #2563eb; text-decoration: underline; font-weight: 600;">
874
- Open Voice Screening Page β†’ {voice_screening_url}
875
- </a>
876
- <br><small style="color: #64748b;">Candidates can access this page to complete their voice screening interview.</small>
877
- </div>
878
- """)
879
 
880
  with gr.Row():
881
  voice_email = gr.Textbox(label="Candidate Email", placeholder="candidate@example.com", scale=3)
@@ -895,7 +924,100 @@ def create_app():
895
  interview_btn.click(fn=schedule_interview, inputs=interview_email, outputs=interview_output)
896
 
897
  # ============================================================
898
- # TAB 3: Supervisor Chat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
  # ============================================================
900
  with gr.Tab("πŸ€– Supervisor Chat"):
901
  gr.Markdown("## πŸ’¬ Chat with HR Supervisor Agent")
 
9
  import sys
10
  from pathlib import Path
11
  import requests
12
+ import uuid
13
 
14
  project_root = Path(__file__).resolve().parent.parent.parent.parent
15
  sys.path.insert(0, str(project_root))
 
62
  voice_url = os.getenv("VOICE_SCREENING_UI_URL", "http://localhost:8502")
63
  return voice_url
64
 
65
+ def get_proxy_url(for_client: bool = False) -> str:
66
+ """Get WebSocket proxy URL from environment or default."""
67
+ proxy_url = os.getenv("WEBSOCKET_PROXY_URL", "ws://localhost:8000/ws/realtime")
68
+ if for_client:
69
+ if "websocket_proxy" in proxy_url:
70
+ proxy_url = proxy_url.replace("websocket_proxy", "localhost")
71
+ return proxy_url
72
+
73
+ def get_proxy_base_url() -> str:
74
+ """Get HTTP base URL for proxy API calls."""
75
+ proxy_url = get_proxy_url(for_client=False)
76
+ return proxy_url.replace("ws://", "http://").replace("wss://", "https://").replace("/ws/realtime", "")
77
+
78
+ def authenticate_voice_screening(email: str, auth_code: str) -> Tuple[str, Optional[str], Optional[str]]:
79
+ """Authenticate user for voice screening. Returns (status_message, session_token, candidate_id)."""
80
+ if not email or not auth_code:
81
+ return "❌ Please enter both email and authentication code.", None, None
82
+ try:
83
+ proxy_base = get_proxy_base_url()
84
+ response = requests.post(
85
+ f"{proxy_base}/auth/verify",
86
+ json={"email": email, "code": auth_code},
87
+ timeout=5
88
+ )
89
+ if response.status_code == 200:
90
+ data = response.json()
91
+ session_token = data.get("session_token")
92
+ candidate_id = data.get("candidate_id")
93
+ return f"βœ… Authentication successful! Session token: {session_token[:20]}...", session_token, candidate_id
94
+ else:
95
+ error_data = response.json() if response.content else {}
96
+ return f"❌ Authentication failed: {error_data.get('detail', response.text)}", None, None
97
+ except Exception as e:
98
+ return f"❌ Error connecting to proxy: {str(e)}", None, None
99
+
100
  # ============================================================================
101
  # CANDIDATE APPLICATION PORTAL
102
  # ============================================================================
 
867
  submit_btn.click(
868
  fn=submit_application,
869
  inputs=[full_name, email, phone, cv_file],
870
+ outputs=application_output,
871
+ show_progress="full"
872
  )
873
 
874
  gr.Markdown("---")
 
901
 
902
  gr.Markdown("---")
903
  gr.Markdown("## πŸŽ™οΈ Voice Screening")
904
+ gr.HTML('''<div class="info-box">
905
+ <strong>Note:</strong> Use the "πŸŽ™οΈ Voice Screening" tab to access the voice interview interface.
906
+ Trigger voice screening for a candidate below to generate their authentication code.
907
+ </div>''')
 
 
 
 
 
 
 
 
908
 
909
  with gr.Row():
910
  voice_email = gr.Textbox(label="Candidate Email", placeholder="candidate@example.com", scale=3)
 
924
  interview_btn.click(fn=schedule_interview, inputs=interview_email, outputs=interview_output)
925
 
926
  # ============================================================
927
+ # TAB 3: Voice Screening
928
+ # ============================================================
929
+ with gr.Tab("πŸŽ™οΈ Voice Screening"):
930
+ gr.Markdown("## πŸŽ™οΈ Voice Screening Interview")
931
+ gr.HTML('''<div class="info-box">
932
+ <strong>Instructions:</strong> Enter your email and authentication code to start the voice screening interview.
933
+ You will receive the authentication code when HR triggers voice screening for you.
934
+ </div>''')
935
+
936
+ # Authentication section
937
+ with gr.Row():
938
+ with gr.Column():
939
+ gr.Markdown("### πŸ” Authentication")
940
+ vs_email = gr.Textbox(label="Email", placeholder="your.email@example.com")
941
+ vs_auth_code = gr.Textbox(label="Authentication Code", placeholder="Enter your 6-digit code", type="password")
942
+ vs_auth_btn = gr.Button("βœ… Authenticate", variant="primary")
943
+ vs_auth_status = gr.Markdown()
944
+
945
+ # Hidden components for session management
946
+ vs_session_token = gr.State()
947
+ vs_candidate_id = gr.State()
948
+ vs_session_id = gr.State()
949
+
950
+ # Voice interface (shown after authentication)
951
+ voice_interface_row = gr.Row(visible=False)
952
+ with voice_interface_row:
953
+ with gr.Column():
954
+ gr.Markdown("### 🎀 Voice Interview")
955
+ voice_interface_html = gr.HTML()
956
+ transcript_display = gr.Markdown("### πŸ“ Transcript\n\n*Transcript will appear here during the interview...*")
957
+
958
+ with gr.Row():
959
+ start_interview_btn = gr.Button("πŸš€ Start Interview", variant="primary")
960
+ end_interview_btn = gr.Button("⏹️ End Interview", variant="secondary")
961
+
962
+ def handle_authentication(email: str, auth_code: str):
963
+ """Handle voice screening authentication."""
964
+ status_msg, session_token, candidate_id = authenticate_voice_screening(email, auth_code)
965
+ if session_token:
966
+ session_id = str(uuid.uuid4())
967
+ return (
968
+ status_msg,
969
+ gr.update(visible=True), # Show voice interface
970
+ session_token,
971
+ candidate_id or "",
972
+ session_id
973
+ )
974
+ return (
975
+ status_msg,
976
+ gr.update(visible=False), # Hide voice interface
977
+ None,
978
+ None,
979
+ None
980
+ )
981
+
982
+ def start_interview(session_token, candidate_id, session_id):
983
+ """Start the voice interview and load HTML component."""
984
+ if not session_token:
985
+ return "❌ Please authenticate first.", gr.HTML()
986
+
987
+ # Load the HTML component
988
+ html_file_path = Path(__file__).parent.parent / "streamlit" / "voice_screening_ui" / "components" / "voice_interface.html"
989
+
990
+ if not html_file_path.exists():
991
+ return "❌ Voice interface component not found.", gr.HTML("<p>Voice interface HTML file not found.</p>")
992
+
993
+ with open(html_file_path, 'r', encoding='utf-8') as f:
994
+ html_content = f.read()
995
+
996
+ # Get proxy URL
997
+ proxy_url = get_proxy_url(for_client=True)
998
+ ws_url = f"{proxy_url}?token={session_token}"
999
+
1000
+ # Replace placeholders
1001
+ html_content = html_content.replace("{{SESSION_ID}}", session_id or "")
1002
+ html_content = html_content.replace("{{SESSION_TOKEN}}", session_token)
1003
+ html_content = html_content.replace("{{PROXY_URL}}", ws_url)
1004
+
1005
+ return "βœ… Interview started! Use the microphone button to speak.", gr.HTML(html_content)
1006
+
1007
+ vs_auth_btn.click(
1008
+ fn=handle_authentication,
1009
+ inputs=[vs_email, vs_auth_code],
1010
+ outputs=[vs_auth_status, voice_interface_row, vs_session_token, vs_candidate_id, vs_session_id]
1011
+ )
1012
+
1013
+ start_interview_btn.click(
1014
+ fn=start_interview,
1015
+ inputs=[vs_session_token, vs_candidate_id, vs_session_id],
1016
+ outputs=[vs_auth_status, voice_interface_html]
1017
+ )
1018
+
1019
+ # ============================================================
1020
+ # TAB 4: Supervisor Chat
1021
  # ============================================================
1022
  with gr.Tab("πŸ€– Supervisor Chat"):
1023
  gr.Markdown("## πŸ’¬ Chat with HR Supervisor Agent")