| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Alexa - Live Connect</title> |
| <style> |
| body { margin: 0; overflow: hidden; background-color: #0a0a0a; color: #00ff00; font-family: 'Courier New', Courier, monospace; } |
| canvas { display: block; } |
| #overlay-container { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 20; } |
| #start-button { font-size: 1.5em; padding: 15px 30px; background-color: #00ff00; color: #0a0a0a; border: none; cursor: pointer; font-family: 'Courier New', Courier, monospace; box-shadow: 0 0 20px rgba(0, 255, 0, 0.7); } |
| #start-button:hover { background-color: #55ff55; } |
| #status { margin-top: 20px; font-size: 1.2em; text-shadow: 0 0 5px #00ff00; } |
| #chat-container { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); width: 80%; max-width: 600px; z-index: 10; display: flex; flex-direction: column; align-items: center; visibility: hidden; } |
| #chat-input { width: 100%; padding: 10px; border: none; border-radius: 5px; background-color: rgba(50, 50, 70, 0.8); color: #00ff00; font-family: 'Courier New', Courier, monospace; font-size: 1em; outline: none; box-shadow: 0 0 10px rgba(0, 255, 0, 0.5); } |
| #chat-input::placeholder { color: rgba(0, 255, 0, 0.5); } |
| </style> |
| </head> |
| <body> |
|
|
| <canvas id="three-canvas"></canvas> |
|
|
| |
| <div id="overlay-container"> |
| <button id="start-button">Click to Awaken Alexa</button> |
| <div id="status">Waiting to connect...</div> |
| </div> |
|
|
| <div id="chat-container"> |
| <input type="text" id="chat-input" placeholder="Say something..."> |
| </div> |
|
|
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.158.0/three.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script> |
|
|
| <script> |
| |
| const canvas = document.getElementById('three-canvas'); |
| const chatInput = document.getElementById('chat-input'); |
| const startButton = document.getElementById('start-button'); |
| const statusDiv = document.getElementById('status'); |
| const overlay = document.getElementById('overlay-container'); |
| const chatContainer = document.getElementById('chat-container'); |
| |
| let chatMessages = []; |
| const maxMessages = 10; |
| let scene, camera, renderer, terminalMesh, terminalTexture, textContext; |
| let mediaRecorder; |
| let audioContext; |
| let audioQueue = []; |
| let isPlaying = false; |
| |
| |
| const socket = io(); |
| |
| |
| function initThree() { |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0x0a0a0a); |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
| camera.position.set(0, 1.5, 3.5); |
| renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true }); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| |
| const ambientLight = new THREE.AmbientLight(0x404040, 2); |
| scene.add(ambientLight); |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); |
| scene.add(directionalLight); |
| |
| const terminalGeometry = new THREE.BoxGeometry(4, 2.5, 0.1); |
| const textCanvas = document.createElement('canvas'); |
| textCanvas.width = 512; |
| textCanvas.height = 512; |
| textContext = textCanvas.getContext('2d'); |
| terminalTexture = new THREE.CanvasTexture(textCanvas); |
| |
| const terminalMaterial = new THREE.MeshStandardMaterial({ |
| map: terminalTexture, |
| color: 0x222222, |
| emissive: 0x00ff00, |
| emissiveIntensity: 0.8, |
| metalness: 0.1, |
| roughness: 0.8, |
| }); |
| |
| terminalMesh = new THREE.Mesh(terminalGeometry, terminalMaterial); |
| terminalMesh.position.set(0, 1.5, 0); |
| scene.add(terminalMesh); |
| |
| window.addEventListener('resize', () => { |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| }, false); |
| |
| animate(); |
| addMessageToTerminal("System: Initializing..."); |
| } |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| terminalMesh.rotation.y += 0.0005; |
| renderer.render(scene, camera); |
| } |
| |
| function updateTerminalTexture() { |
| textContext.fillStyle = '#0a0a0a'; |
| textContext.fillRect(0, 0, textContext.canvas.width, textContext.canvas.height); |
| textContext.font = 'Bold 30px Courier New'; |
| textContext.fillStyle = '#00ff00'; |
| textContext.textAlign = 'left'; |
| textContext.textBaseline = 'top'; |
| |
| const padding = 20; |
| const lineHeight = 35; |
| let y = padding; |
| |
| chatMessages.forEach(msg => { |
| textContext.fillText(msg, padding, y); |
| y += lineHeight; |
| }); |
| terminalTexture.needsUpdate = true; |
| } |
| |
| function addMessageToTerminal(message) { |
| chatMessages.push(message); |
| if (chatMessages.length > maxMessages) { |
| chatMessages.shift(); |
| } |
| updateTerminalTexture(); |
| } |
| |
| |
| async function startAudioProcessing() { |
| |
| |
| audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 }); |
| |
| |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
| |
| |
| |
| mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' }); |
| |
| mediaRecorder.ondataavailable = (event) => { |
| if (event.data.size > 0) { |
| socket.emit('client_audio', event.data); |
| } |
| }; |
| |
| mediaRecorder.start(500); |
| statusDiv.textContent = 'Connection active. Speak now!'; |
| } |
| |
| function playNextInQueue() { |
| if (isPlaying || audioQueue.length === 0) { |
| return; |
| } |
| isPlaying = true; |
| const audioData = audioQueue.shift(); |
| |
| |
| audioContext.decodeAudioData(audioData, (buffer) => { |
| const source = audioContext.createBufferSource(); |
| source.buffer = buffer; |
| source.connect(audioContext.destination); |
| source.onended = () => { |
| isPlaying = false; |
| playNextInQueue(); |
| }; |
| source.start(); |
| }, (error) => { |
| console.error('Error decoding audio data:', error); |
| isPlaying = false; |
| playNextInQueue(); |
| }); |
| } |
| |
| |
| startButton.addEventListener('click', async () => { |
| statusDiv.textContent = "Connecting to Alexa..."; |
| try { |
| |
| initThree(); |
| await startAudioProcessing(); |
| overlay.style.display = 'none'; |
| chatContainer.style.visibility = 'visible'; |
| } catch (error) { |
| console.error("Error starting session:", error); |
| statusDiv.textContent = "Error: Could not access microphone."; |
| addMessageToTerminal("Error: Mic access denied."); |
| } |
| }); |
| |
| chatInput.addEventListener('keypress', (event) => { |
| if (event.key === 'Enter') { |
| const message = chatInput.value.trim(); |
| if (message) { |
| addMessageToTerminal("You: " + message); |
| socket.emit('client_text', { text: message }); |
| chatInput.value = ''; |
| } |
| } |
| }); |
| |
| |
| socket.on('connect', () => { |
| console.log('Socket.IO connected!'); |
| statusDiv.textContent = 'Connected. Waiting for session...'; |
| }); |
| |
| socket.on('session_ready', () => { |
| console.log('Gemini session is ready on the server.'); |
| addMessageToTerminal('Alexa: Hey there! What\'s on your mind? ;)'); |
| }); |
| |
| socket.on('server_text', (data) => { |
| console.log('Received text:', data.text); |
| addMessageToTerminal("Alexa: " + data.text); |
| }); |
| |
| socket.on('server_audio', (data) => { |
| |
| audioQueue.push(data); |
| playNextInQueue(); |
| }); |
| |
| socket.on('disconnect', () => { |
| console.log('Socket.IO disconnected.'); |
| statusDiv.textContent = 'Disconnected.'; |
| addMessageToTerminal("System: Connection lost."); |
| if (mediaRecorder && mediaRecorder.state === 'recording') { |
| mediaRecorder.stop(); |
| } |
| }); |
| |
| socket.on('error', (data) => { |
| console.error('Server error:', data.message); |
| statusDiv.textContent = `Error: ${data.message}`; |
| addMessageToTerminal(`System Error: ${data.message}`); |
| }); |
| |
| </script> |
| </body> |
| </html> |