| class AIDialog extends HTMLElement { |
| constructor() { |
| super(); |
| this.attachShadow({ mode: 'open' }); |
| this.partnerVideo = null; |
| this.userVideo = null; |
| this.mediaStream = null; |
| this.peerConnection = null; |
| this.aiMessages = []; |
| this.userMessages = []; |
| this.dialogAnalysis = null; |
| } |
|
|
| connectedCallback() { |
| this.shadowRoot.innerHTML = ` |
| <style> |
| :host { |
| display: block; |
| margin: 2rem 0; |
| } |
| .container { |
| background: rgba(15, 23, 42, 0.7); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| border-radius: 1rem; |
| padding: 2rem; |
| } |
| .header { |
| display: flex; |
| align-items: center; |
| margin-bottom: 1.5rem; |
| } |
| .icon { |
| width: 3rem; |
| height: 3rem; |
| background: rgba(124, 58, 237, 0.2); |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-right: 1rem; |
| } |
| h2 { |
| font-size: 1.5rem; |
| font-weight: 600; |
| margin: 0; |
| background: linear-gradient(90deg, #7c3aed 0%, #2563eb 100%); |
| -webkit-background-clip: text; |
| background-clip: text; |
| color: transparent; |
| } |
| .video-container { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 1rem; |
| margin-bottom: 1.5rem; |
| } |
| .video-box { |
| position: relative; |
| border-radius: 0.5rem; |
| overflow: hidden; |
| background: #1e293b; |
| aspect-ratio: 16/9; |
| } |
| video { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| .video-label { |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| background: rgba(0,0,0,0.7); |
| padding: 0.5rem; |
| font-size: 0.875rem; |
| text-align: center; |
| } |
| .controls { |
| display: flex; |
| gap: 1rem; |
| margin-bottom: 1.5rem; |
| } |
| button { |
| flex: 1; |
| padding: 0.75rem 1.5rem; |
| border-radius: 0.5rem; |
| font-weight: 500; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 0.5rem; |
| cursor: pointer; |
| transition: all 0.2s; |
| border: none; |
| } |
| .start-btn { |
| background: #7c3aed; |
| color: white; |
| } |
| .start-btn:hover { |
| background: #6d28d9; |
| } |
| .end-btn { |
| background: #1e293b; |
| color: white; |
| border: 1px solid #334155; |
| } |
| .end-btn:hover { |
| background: #334155; |
| } |
| .analysis { |
| margin-top: 1.5rem; |
| display: none; |
| } |
| .metric { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 0.75rem; |
| padding-bottom: 0.75rem; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| .metric-label { |
| font-weight: 500; |
| } |
| .metric-value { |
| font-weight: 600; |
| color: #7c3aed; |
| } |
| .progress-bar { |
| height: 8px; |
| border-radius: 4px; |
| background: rgba(124, 58, 237, 0.2); |
| margin-top: 0.5rem; |
| } |
| .progress-fill { |
| height: 100%; |
| border-radius: 4px; |
| background: linear-gradient(90deg, #7c3aed 0%, #2563eb 100%); |
| width: 0%; |
| transition: width 0.3s ease; |
| } |
| .feedback { |
| margin-top: 1rem; |
| padding: 1rem; |
| background: rgba(30, 41, 59, 0.5); |
| border-radius: 0.5rem; |
| } |
| </style> |
| <div class="container"> |
| <div class="header"> |
| <div class="icon"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path> |
| <circle cx="9" cy="7" r="4"></circle> |
| <path d="M23 21v-2a4 4 0 0 0-3-3.87"></path> |
| <path d="M16 3.13a4 4 0 0 1 0 7.75"></path> |
| </svg> |
| </div> |
| <h2>AI Dialog Partner</h2> |
| </div> |
| |
| <p>Practice conversational skills with our AI partner. The system will analyze your performance.</p> |
| |
| <div class="video-container"> |
| <div class="video-box"> |
| <video id="partnerVideo" autoplay muted></video> |
| <div class="video-label">AI Partner</div> |
| </div> |
| <div class="video-box"> |
| <video id="userVideo" autoplay></video> |
| <div class="video-label">You</div> |
| </div> |
| </div> |
| |
| <div class="controls"> |
| <button class="start-btn" id="startBtn"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <circle cx="12" cy="12" r="3"></circle> |
| </svg> |
| Start Conversation |
| </button> |
| <button class="end-btn" id="endBtn" disabled> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <rect x="6" y="4" width="4" height="16"></rect> |
| <rect x="14" y="4" width="4" height="16"></rect> |
| </svg> |
| End Session |
| </button> |
| </div> |
| |
| <div class="analysis" id="analysis"> |
| <h3>Dialog Analysis</h3> |
| <div class="metric"> |
| <span class="metric-label">Fluency</span> |
| <span class="metric-value" id="fluencyScore">0%</span> |
| </div> |
| <div class="progress-bar"> |
| <div class="progress-fill" id="fluencyBar"></div> |
| </div> |
| |
| <div class="metric"> |
| <span class="metric-label">Vocabulary</span> |
| <span class="metric-value" id="vocabularyScore">0%</span> |
| </div> |
| <div class="progress-bar"> |
| <div class="progress-fill" id="vocabularyBar"></div> |
| </div> |
| |
| <div class="metric"> |
| <span class="metric-label">Pronunciation</span> |
| <span class="metric-value" id="pronunciationScore">0%</span> |
| </div> |
| <div class="progress-bar"> |
| <div class="progress-fill" id="pronunciationBar"></div> |
| </div> |
| |
| <div class="feedback"> |
| <h4>Feedback:</h4> |
| <p id="feedbackText">Complete a conversation to receive feedback.</p> |
| </div> |
| </div> |
| </div> |
| `; |
|
|
| this.partnerVideo = this.shadowRoot.getElementById('partnerVideo'); |
| this.userVideo = this.shadowRoot.getElementById('userVideo'); |
| this.startBtn = this.shadowRoot.getElementById('startBtn'); |
| this.endBtn = this.shadowRoot.getElementById('endBtn'); |
| this.analysisSection = this.shadowRoot.getElementById('analysis'); |
|
|
| this.setupEventListeners(); |
| } |
|
|
| setupEventListeners() { |
| this.startBtn.addEventListener('click', () => this.startConversation()); |
| this.endBtn.addEventListener('click', () => this.endConversation()); |
| } |
|
|
| async startConversation() { |
| try { |
| |
| this.mediaStream = await navigator.mediaDevices.getUserMedia({ |
| video: true, |
| audio: true |
| }); |
| this.userVideo.srcObject = this.mediaStream; |
|
|
| |
| this.peerConnection = new RTCPeerConnection(); |
| this.mediaStream.getTracks().forEach(track => { |
| this.peerConnection.addTrack(track, this.mediaStream); |
| }); |
|
|
| |
| this.partnerVideo.src = 'https://static.photos/people/640x360/1'; |
|
|
| this.startBtn.disabled = true; |
| this.endBtn.disabled = false; |
|
|
| |
| this.simulateAIDialog(); |
| } catch (error) { |
| console.error('Error starting conversation:', error); |
| alert('Could not access camera/microphone. Please check permissions.'); |
| } |
| } |
|
|
| async endConversation() { |
| |
| if (this.mediaStream) { |
| this.mediaStream.getTracks().forEach(track => track.stop()); |
| } |
|
|
| |
| if (this.peerConnection) { |
| this.peerConnection.close(); |
| } |
|
|
| this.userVideo.srcObject = null; |
| this.partnerVideo.src = null; |
|
|
| this.startBtn.disabled = false; |
| this.endBtn.disabled = true; |
|
|
| |
| this.analyzeDialog(); |
| } |
|
|
| simulateAIDialog() { |
| |
| const aiResponses = [ |
| "Hello! How are you today?", |
| "That's interesting. Tell me more about that.", |
| "I understand how you feel. What would you like to discuss next?", |
| "Your pronunciation is improving! Keep practicing.", |
| "Let's try to use some new vocabulary words." |
| ]; |
|
|
| let responseIndex = 0; |
| this.aiMessages = []; |
| |
| const aiInterval = setInterval(() => { |
| if (!this.endBtn.disabled) { |
| const response = aiResponses[responseIndex % aiResponses.length]; |
| this.aiMessages.push(response); |
| responseIndex++; |
| |
| |
| const feedback = this.shadowRoot.getElementById('feedbackText'); |
| feedback.textContent = `AI: ${response}`; |
| } else { |
| clearInterval(aiInterval); |
| } |
| }, 8000); |
| } |
|
|
| analyzeDialog() { |
| |
| this.dialogAnalysis = { |
| fluency: Math.floor(Math.random() * 30) + 70, |
| vocabulary: Math.floor(Math.random() * 30) + 70, |
| pronunciation: Math.floor(Math.random() * 30) + 70, |
| feedback: "Good job! You showed improvement in fluency. Try to use more complex sentences next time." |
| }; |
|
|
| |
| this.analysisSection.style.display = 'block'; |
| |
| this.shadowRoot.getElementById('fluencyScore').textContent = `${this.dialogAnalysis.fluency}%`; |
| this.shadowRoot.getElementById('fluencyBar').style.width = `${this.dialogAnalysis.fluency}%`; |
| |
| this.shadowRoot.getElementById('vocabularyScore').textContent = `${this.dialogAnalysis.vocabulary}%`; |
| this.shadowRoot.getElementById('vocabularyBar').style.width = `${this.dialogAnalysis.vocabulary}%`; |
| |
| this.shadowRoot.getElementById('pronunciationScore').textContent = `${this.dialogAnalysis.pronunciation}%`; |
| this.shadowRoot.getElementById('pronunciationBar').style.width = `${this.dialogAnalysis.pronunciation}%`; |
| |
| this.shadowRoot.getElementById('feedbackText').textContent = this.dialogAnalysis.feedback; |
| } |
| } |
|
|
| customElements.define('ai-dialog', AIDialog); |