Match01 commited on
Commit
ab305ca
·
verified ·
1 Parent(s): 1cc44b9

Create index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +247 -0
templates/index.html ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Alexa - Live Connect</title>
7
+ <style>
8
+ body { margin: 0; overflow: hidden; background-color: #0a0a0a; color: #00ff00; font-family: 'Courier New', Courier, monospace; }
9
+ canvas { display: block; }
10
+ #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; }
11
+ #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); }
12
+ #start-button:hover { background-color: #55ff55; }
13
+ #status { margin-top: 20px; font-size: 1.2em; text-shadow: 0 0 5px #00ff00; }
14
+ #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; /* Hidden by default */ }
15
+ #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); }
16
+ #chat-input::placeholder { color: rgba(0, 255, 0, 0.5); }
17
+ </style>
18
+ </head>
19
+ <body>
20
+
21
+ <canvas id="three-canvas"></canvas>
22
+
23
+ <!-- Overlay for starting the session -->
24
+ <div id="overlay-container">
25
+ <button id="start-button">Click to Awaken Alexa</button>
26
+ <div id="status">Waiting to connect...</div>
27
+ </div>
28
+
29
+ <div id="chat-container">
30
+ <input type="text" id="chat-input" placeholder="Say something...">
31
+ </div>
32
+
33
+ <!-- Dependencies -->
34
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.158.0/three.min.js"></script>
35
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script>
36
+
37
+ <script>
38
+ // --- Global Variables ---
39
+ const canvas = document.getElementById('three-canvas');
40
+ const chatInput = document.getElementById('chat-input');
41
+ const startButton = document.getElementById('start-button');
42
+ const statusDiv = document.getElementById('status');
43
+ const overlay = document.getElementById('overlay-container');
44
+ const chatContainer = document.getElementById('chat-container');
45
+
46
+ let chatMessages = [];
47
+ const maxMessages = 10;
48
+ let scene, camera, renderer, terminalMesh, terminalTexture, textContext;
49
+ let mediaRecorder;
50
+ let audioContext;
51
+ let audioQueue = [];
52
+ let isPlaying = false;
53
+
54
+ // --- Socket.IO Connection ---
55
+ const socket = io();
56
+
57
+ // --- Core 3D Setup (from your original code, slightly adapted) ---
58
+ function initThree() {
59
+ scene = new THREE.Scene();
60
+ scene.background = new THREE.Color(0x0a0a0a);
61
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
62
+ camera.position.set(0, 1.5, 3.5);
63
+ renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
64
+ renderer.setSize(window.innerWidth, window.innerHeight);
65
+
66
+ const ambientLight = new THREE.AmbientLight(0x404040, 2);
67
+ scene.add(ambientLight);
68
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
69
+ scene.add(directionalLight);
70
+
71
+ const terminalGeometry = new THREE.BoxGeometry(4, 2.5, 0.1);
72
+ const textCanvas = document.createElement('canvas');
73
+ textCanvas.width = 512;
74
+ textCanvas.height = 512;
75
+ textContext = textCanvas.getContext('2d');
76
+ terminalTexture = new THREE.CanvasTexture(textCanvas);
77
+
78
+ const terminalMaterial = new THREE.MeshStandardMaterial({
79
+ map: terminalTexture,
80
+ color: 0x222222,
81
+ emissive: 0x00ff00,
82
+ emissiveIntensity: 0.8,
83
+ metalness: 0.1,
84
+ roughness: 0.8,
85
+ });
86
+
87
+ terminalMesh = new THREE.Mesh(terminalGeometry, terminalMaterial);
88
+ terminalMesh.position.set(0, 1.5, 0);
89
+ scene.add(terminalMesh);
90
+
91
+ window.addEventListener('resize', () => {
92
+ camera.aspect = window.innerWidth / window.innerHeight;
93
+ camera.updateProjectionMatrix();
94
+ renderer.setSize(window.innerWidth, window.innerHeight);
95
+ }, false);
96
+
97
+ animate();
98
+ addMessageToTerminal("System: Initializing...");
99
+ }
100
+
101
+ function animate() {
102
+ requestAnimationFrame(animate);
103
+ terminalMesh.rotation.y += 0.0005;
104
+ renderer.render(scene, camera);
105
+ }
106
+
107
+ function updateTerminalTexture() {
108
+ textContext.fillStyle = '#0a0a0a';
109
+ textContext.fillRect(0, 0, textContext.canvas.width, textContext.canvas.height);
110
+ textContext.font = 'Bold 30px Courier New';
111
+ textContext.fillStyle = '#00ff00';
112
+ textContext.textAlign = 'left';
113
+ textContext.textBaseline = 'top';
114
+
115
+ const padding = 20;
116
+ const lineHeight = 35;
117
+ let y = padding;
118
+
119
+ chatMessages.forEach(msg => {
120
+ textContext.fillText(msg, padding, y);
121
+ y += lineHeight;
122
+ });
123
+ terminalTexture.needsUpdate = true;
124
+ }
125
+
126
+ function addMessageToTerminal(message) {
127
+ chatMessages.push(message);
128
+ if (chatMessages.length > maxMessages) {
129
+ chatMessages.shift();
130
+ }
131
+ updateTerminalTexture();
132
+ }
133
+
134
+ // --- Web Audio and Media Recorder Setup ---
135
+ async function startAudioProcessing() {
136
+ // 1. Create AudioContext for playback (required by browsers to start after user interaction)
137
+ // The Gemini model sends audio at 24000 Hz.
138
+ audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 24000 });
139
+
140
+ // 2. Get microphone access
141
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
142
+
143
+ // 3. Setup MediaRecorder to capture audio and send it to the server
144
+ // The `timeslice` parameter makes it emit data every 500ms.
145
+ mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' });
146
+
147
+ mediaRecorder.ondataavailable = (event) => {
148
+ if (event.data.size > 0) {
149
+ socket.emit('client_audio', event.data);
150
+ }
151
+ };
152
+
153
+ mediaRecorder.start(500); // Send data every 500ms
154
+ statusDiv.textContent = 'Connection active. Speak now!';
155
+ }
156
+
157
+ function playNextInQueue() {
158
+ if (isPlaying || audioQueue.length === 0) {
159
+ return;
160
+ }
161
+ isPlaying = true;
162
+ const audioData = audioQueue.shift();
163
+
164
+ // Decode the ArrayBuffer into an AudioBuffer
165
+ audioContext.decodeAudioData(audioData, (buffer) => {
166
+ const source = audioContext.createBufferSource();
167
+ source.buffer = buffer;
168
+ source.connect(audioContext.destination);
169
+ source.onended = () => {
170
+ isPlaying = false;
171
+ playNextInQueue(); // Play the next chunk when this one is done
172
+ };
173
+ source.start();
174
+ }, (error) => {
175
+ console.error('Error decoding audio data:', error);
176
+ isPlaying = false;
177
+ playNextInQueue(); // Skip bad chunk
178
+ });
179
+ }
180
+
181
+ // --- Event Listeners ---
182
+ startButton.addEventListener('click', async () => {
183
+ statusDiv.textContent = "Connecting to Alexa...";
184
+ try {
185
+ // Initialize 3D scene and audio after user clicks
186
+ initThree();
187
+ await startAudioProcessing();
188
+ overlay.style.display = 'none'; // Hide the start button
189
+ chatContainer.style.visibility = 'visible'; // Show the chat input
190
+ } catch (error) {
191
+ console.error("Error starting session:", error);
192
+ statusDiv.textContent = "Error: Could not access microphone.";
193
+ addMessageToTerminal("Error: Mic access denied.");
194
+ }
195
+ });
196
+
197
+ chatInput.addEventListener('keypress', (event) => {
198
+ if (event.key === 'Enter') {
199
+ const message = chatInput.value.trim();
200
+ if (message) {
201
+ addMessageToTerminal("You: " + message);
202
+ socket.emit('client_text', { text: message });
203
+ chatInput.value = '';
204
+ }
205
+ }
206
+ });
207
+
208
+ // --- SocketIO Event Listeners ---
209
+ socket.on('connect', () => {
210
+ console.log('Socket.IO connected!');
211
+ statusDiv.textContent = 'Connected. Waiting for session...';
212
+ });
213
+
214
+ socket.on('session_ready', () => {
215
+ console.log('Gemini session is ready on the server.');
216
+ addMessageToTerminal('Alexa: Hey there! What\'s on your mind? ;)');
217
+ });
218
+
219
+ socket.on('server_text', (data) => {
220
+ console.log('Received text:', data.text);
221
+ addMessageToTerminal("Alexa: " + data.text);
222
+ });
223
+
224
+ socket.on('server_audio', (data) => {
225
+ // data is an ArrayBuffer. Add it to our queue for smooth playback.
226
+ audioQueue.push(data);
227
+ playNextInQueue();
228
+ });
229
+
230
+ socket.on('disconnect', () => {
231
+ console.log('Socket.IO disconnected.');
232
+ statusDiv.textContent = 'Disconnected.';
233
+ addMessageToTerminal("System: Connection lost.");
234
+ if (mediaRecorder && mediaRecorder.state === 'recording') {
235
+ mediaRecorder.stop();
236
+ }
237
+ });
238
+
239
+ socket.on('error', (data) => {
240
+ console.error('Server error:', data.message);
241
+ statusDiv.textContent = `Error: ${data.message}`;
242
+ addMessageToTerminal(`System Error: ${data.message}`);
243
+ });
244
+
245
+ </script>
246
+ </body>
247
+ </html>