Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Interview WebSocket Test</title> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 50px auto; | |
| padding: 20px; | |
| background: #f5f5f5; | |
| } | |
| .container { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| } | |
| h1 { | |
| color: #333; | |
| margin-bottom: 20px; | |
| } | |
| .status { | |
| padding: 10px; | |
| margin: 10px 0; | |
| border-radius: 5px; | |
| font-weight: bold; | |
| } | |
| .status.connected { | |
| background: #d4edda; | |
| color: #155724; | |
| } | |
| .status.disconnected { | |
| background: #f8d7da; | |
| color: #721c24; | |
| } | |
| .status.connecting { | |
| background: #fff3cd; | |
| color: #856404; | |
| } | |
| button { | |
| background: #007bff; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| margin: 5px; | |
| font-size: 14px; | |
| } | |
| button:hover { | |
| background: #0056b3; | |
| } | |
| button:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| .messages { | |
| margin-top: 20px; | |
| max-height: 400px; | |
| overflow-y: auto; | |
| border: 1px solid #ddd; | |
| padding: 10px; | |
| border-radius: 5px; | |
| background: #f9f9f9; | |
| } | |
| .message { | |
| padding: 8px; | |
| margin: 5px 0; | |
| border-radius: 3px; | |
| font-size: 13px; | |
| } | |
| .message.sent { | |
| background: #e3f2fd; | |
| border-left: 3px solid #2196f3; | |
| } | |
| .message.received { | |
| background: #f1f8e9; | |
| border-left: 3px solid #8bc34a; | |
| } | |
| .message.error { | |
| background: #ffebee; | |
| border-left: 3px solid #f44336; | |
| } | |
| .timestamp { | |
| color: #666; | |
| font-size: 11px; | |
| } | |
| input { | |
| width: 100%; | |
| padding: 10px; | |
| margin: 10px 0; | |
| border: 1px solid #ddd; | |
| border-radius: 5px; | |
| box-sizing: border-box; | |
| } | |
| .question-box { | |
| background: #fff3cd; | |
| padding: 15px; | |
| margin: 15px 0; | |
| border-radius: 5px; | |
| border-left: 4px solid #ffc107; | |
| } | |
| .score-box { | |
| background: #d4edda; | |
| padding: 15px; | |
| margin: 15px 0; | |
| border-radius: 5px; | |
| border-left: 4px solid #28a745; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🤖 AI Interview WebSocket Test</h1> | |
| <div id="status" class="status disconnected">Disconnected</div> | |
| <div> | |
| <input type="text" id="sessionId" placeholder="Session ID (leave empty to create new)"> | |
| <button id="connectBtn" onclick="connect()">Connect</button> | |
| <button id="disconnectBtn" onclick="disconnect()" disabled>Disconnect</button> | |
| </div> | |
| <div style="margin-top: 20px;"> | |
| <button id="startBtn" onclick="startInterview()" disabled>Start Interview</button> | |
| <button id="pingBtn" onclick="sendPing()" disabled>Send Ping</button> | |
| <button id="endBtn" onclick="endInterview()" disabled>End Interview</button> | |
| </div> | |
| <div id="questionBox" class="question-box" style="display: none;"> | |
| <h3>Current Question:</h3> | |
| <p id="currentQuestion"></p> | |
| <input type="text" id="answerInput" placeholder="Type your answer here..."> | |
| <button onclick="submitAnswer()">Submit Answer</button> | |
| </div> | |
| <div id="scoreBox" class="score-box" style="display: none;"> | |
| <h3>Latest Scores:</h3> | |
| <div id="scores"></div> | |
| </div> | |
| <div class="messages" id="messages"></div> | |
| </div> | |
| <script> | |
| let ws = null; | |
| let currentQuestionNumber = 0; | |
| const API_BASE = 'http://localhost:8000/api/v1'; | |
| const WS_BASE = 'ws://localhost:8000/api/v1'; | |
| function updateStatus(status, className) { | |
| const statusEl = document.getElementById('status'); | |
| statusEl.textContent = status; | |
| statusEl.className = 'status ' + className; | |
| } | |
| function addMessage(text, type = 'received') { | |
| const messagesEl = document.getElementById('messages'); | |
| const messageEl = document.createElement('div'); | |
| messageEl.className = 'message ' + type; | |
| const timestamp = new Date().toLocaleTimeString(); | |
| messageEl.innerHTML = ` | |
| <div>${text}</div> | |
| <div class="timestamp">${timestamp}</div> | |
| `; | |
| messagesEl.appendChild(messageEl); | |
| messagesEl.scrollTop = messagesEl.scrollHeight; | |
| } | |
| async function createSession() { | |
| try { | |
| // For testing, we'll use a hardcoded token | |
| // In production, get this from login | |
| const token = localStorage.getItem('hirely_token') || 'test_token'; | |
| const response = await fetch(`${API_BASE}/ai-interview/session/create?interview_id=1`, { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${token}` | |
| } | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to create session'); | |
| } | |
| const data = await response.json(); | |
| return data.session_id; | |
| } catch (error) { | |
| addMessage('Error creating session: ' + error.message, 'error'); | |
| return null; | |
| } | |
| } | |
| async function connect() { | |
| let sessionId = document.getElementById('sessionId').value.trim(); | |
| if (!sessionId) { | |
| addMessage('Creating new session...', 'sent'); | |
| sessionId = await createSession(); | |
| if (!sessionId) return; | |
| document.getElementById('sessionId').value = sessionId; | |
| addMessage('Session created: ' + sessionId, 'received'); | |
| } | |
| updateStatus('Connecting...', 'connecting'); | |
| addMessage('Connecting to WebSocket...', 'sent'); | |
| const token = localStorage.getItem('hirely_token') || 'test_token'; | |
| const wsUrl = `${WS_BASE}/ai-interview/ws/${sessionId}?token=${token}`; | |
| ws = new WebSocket(wsUrl); | |
| ws.onopen = () => { | |
| updateStatus('Connected', 'connected'); | |
| addMessage('✅ Connected to WebSocket!', 'received'); | |
| document.getElementById('connectBtn').disabled = true; | |
| document.getElementById('disconnectBtn').disabled = false; | |
| document.getElementById('startBtn').disabled = false; | |
| document.getElementById('pingBtn').disabled = false; | |
| document.getElementById('endBtn').disabled = false; | |
| }; | |
| ws.onmessage = (event) => { | |
| const data = JSON.parse(event.data); | |
| handleMessage(data); | |
| }; | |
| ws.onerror = (error) => { | |
| addMessage('❌ WebSocket error: ' + error, 'error'); | |
| }; | |
| ws.onclose = () => { | |
| updateStatus('Disconnected', 'disconnected'); | |
| addMessage('WebSocket closed', 'error'); | |
| document.getElementById('connectBtn').disabled = false; | |
| document.getElementById('disconnectBtn').disabled = true; | |
| document.getElementById('startBtn').disabled = true; | |
| document.getElementById('pingBtn').disabled = true; | |
| document.getElementById('endBtn').disabled = true; | |
| }; | |
| } | |
| function disconnect() { | |
| if (ws) { | |
| ws.close(); | |
| ws = null; | |
| } | |
| } | |
| function handleMessage(data) { | |
| const type = data.type; | |
| addMessage(`📨 Received: ${type}`, 'received'); | |
| switch(type) { | |
| case 'welcome': | |
| addMessage(`Message: ${data.message}`, 'received'); | |
| break; | |
| case 'question': | |
| currentQuestionNumber = data.question_number; | |
| document.getElementById('currentQuestion').textContent = data.question; | |
| document.getElementById('questionBox').style.display = 'block'; | |
| addMessage(`Question ${data.question_number}: ${data.question}`, 'received'); | |
| break; | |
| case 'answer_scored': | |
| const scores = data.scores; | |
| document.getElementById('scores').innerHTML = ` | |
| <p>Clarity: ${scores.clarity}</p> | |
| <p>Relevance: ${scores.relevance}</p> | |
| <p>Conciseness: ${scores.conciseness}</p> | |
| <p>Communication: ${scores.communication}</p> | |
| `; | |
| document.getElementById('scoreBox').style.display = 'block'; | |
| addMessage(`Scores received for Q${data.question_number}`, 'received'); | |
| break; | |
| case 'interview_completed': | |
| addMessage(`✅ Interview completed! Overall score: ${data.overall_score}`, 'received'); | |
| document.getElementById('questionBox').style.display = 'none'; | |
| break; | |
| case 'pong': | |
| addMessage('🏓 Pong received', 'received'); | |
| break; | |
| case 'status': | |
| addMessage(`Status: ${data.status}`, 'received'); | |
| break; | |
| case 'error': | |
| addMessage(`Error: ${data.message}`, 'error'); | |
| break; | |
| } | |
| } | |
| function startInterview() { | |
| if (!ws) return; | |
| addMessage('Starting interview...', 'sent'); | |
| ws.send(JSON.stringify({ | |
| type: 'start_interview' | |
| })); | |
| } | |
| function submitAnswer() { | |
| if (!ws) return; | |
| const answer = document.getElementById('answerInput').value; | |
| if (!answer.trim()) { | |
| alert('Please enter an answer'); | |
| return; | |
| } | |
| addMessage(`Submitting answer: "${answer}"`, 'sent'); | |
| ws.send(JSON.stringify({ | |
| type: 'answer_submitted', | |
| question_number: currentQuestionNumber, | |
| answer: answer | |
| })); | |
| document.getElementById('answerInput').value = ''; | |
| } | |
| function sendPing() { | |
| if (!ws) return; | |
| addMessage('Sending ping...', 'sent'); | |
| ws.send(JSON.stringify({ | |
| type: 'ping' | |
| })); | |
| } | |
| function endInterview() { | |
| if (!ws) return; | |
| addMessage('Ending interview...', 'sent'); | |
| ws.send(JSON.stringify({ | |
| type: 'end_interview' | |
| })); | |
| } | |
| </script> | |
| </body> | |
| </html> | |