Spaces:
Sleeping
Sleeping
Update src/frontend/gradio/app.py
Browse files- 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 |
-
|
| 869 |
-
|
| 870 |
-
|
| 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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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")
|