anshharora commited on
Commit
57227ab
·
verified ·
1 Parent(s): b2fa458

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +186 -220
static/script.js CHANGED
@@ -1,298 +1,264 @@
1
  class ChatBot {
2
  constructor() {
3
- this.voiceEnabled = true;
4
  this.isListening = false;
5
- this.isSpeaking = false;
6
  this.synthesis = window.speechSynthesis;
7
  this.recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
8
- this.silenceTimeout = null;
9
  this.currentUtterance = null;
 
 
 
 
 
10
 
11
  this.setupRecognition();
12
  this.setupEventListeners();
13
  }
14
 
15
  setupRecognition() {
16
- this.recognition.continuous = false;
17
- this.recognition.interimResults = false;
18
  this.recognition.lang = 'en-US';
19
 
20
- this.recognition.onresult = (event) => {
21
- const message = event.results[0][0].transcript;
22
- this.clearInput();
23
- this.sendMessage(message, true);
24
- this.resetSilenceTimer();
25
  };
26
 
27
  this.recognition.onend = () => {
28
  this.isListening = false;
29
  this.toggleVoiceInputClass(false);
30
- if (this.autoListening && !this.isSpeaking) {
31
- this.startListening();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
 
 
 
33
  };
34
  }
35
 
36
  setupEventListeners() {
37
- const messageInput = document.getElementById('messageInput');
38
- messageInput.addEventListener('keydown', (e) => {
39
- if (e.key === 'Enter') {
40
- if (e.shiftKey) return;
41
- e.preventDefault();
42
- const message = messageInput.value.trim();
43
- if (message) {
44
- this.stopSpeaking();
45
- this.stopListening();
46
- this.sendMessage(message, false);
47
- this.clearInput();
48
- }
49
- }
50
- });
51
 
52
- document.getElementById('sendMessage').addEventListener('click', () => {
53
- const message = messageInput.value.trim();
54
- if (message) {
55
- this.stopSpeaking();
56
- this.stopListening();
57
- this.sendMessage(message, false);
58
- this.clearInput();
59
- }
60
  });
61
 
62
- document.getElementById('voiceInput').addEventListener('click', () => {
63
- this.stopSpeaking();
64
- if (this.isListening) {
65
- this.stopListening();
66
- } else {
67
- this.startListening();
68
- }
69
  });
70
 
71
- document.getElementById('chatMessages').addEventListener('click', () => {
72
- if (this.isSpeaking) {
73
- this.stopSpeaking();
 
 
74
  }
75
  });
76
- }
77
 
78
- async sendMessage(message, isVoiceInput) {
79
- this.addMessageToChat(message, 'user');
80
- this.showTypingIndicator();
81
-
82
- try {
83
- const response = await this.callAPI(message);
84
- this.removeTypingIndicator();
85
- this.addMessageToChat(response, 'bot');
86
-
87
- if (this.voiceEnabled) {
88
- this.speak(response);
89
- }
90
- } catch (error) {
91
- console.error('Error:', error);
92
- this.removeTypingIndicator();
93
- this.addMessageToChat('Sorry, there was an error processing your request.', 'bot');
94
- this.isListening = false;
95
- this.toggleVoiceInputClass(false);
96
- }
97
- }
98
-
99
- addMessageToChat(message, sender) {
100
- const messagesContainer = document.getElementById('chatMessages');
101
- const templates = document.querySelector('.message-templates');
102
-
103
- // Clone the appropriate template
104
- const messageTemplate = templates.querySelector(sender === 'bot' ? '.bot-message' : '.user-message').cloneNode(true);
105
-
106
- // Set the message content
107
- messageTemplate.querySelector('.message-content').textContent = message;
108
-
109
- // Add speaker button for bot messages
110
- if (sender === 'bot') {
111
- const speakerButton = document.createElement('button');
112
- speakerButton.className = 'message-speaker';
113
- speakerButton.innerHTML = '<i class="fas fa-volume-up"></i>';
114
-
115
- speakerButton.onclick = () => {
116
- if (this.isSpeaking) {
117
- this.stopSpeaking();
118
- speakerButton.innerHTML = '<i class="fas fa-volume-up"></i>';
119
  } else {
120
  this.stopSpeaking();
121
- this.speak(message, () => {
122
- speakerButton.innerHTML = '<i class="fas fa-volume-up"></i>';
123
- });
124
- speakerButton.innerHTML = '<i class="fas fa-volume-mute"></i>';
125
  }
126
- };
127
-
128
- messageTemplate.querySelector('.message-content').appendChild(speakerButton);
129
- }
130
-
131
- messagesContainer.appendChild(messageTemplate);
132
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
133
- }
134
-
135
- async callAPI(message) {
136
- const response = await fetch('/api/chat', {
137
- method: 'POST',
138
- headers: { 'Content-Type': 'application/json' },
139
- body: JSON.stringify({ message })
140
  });
141
- if (!response.ok) throw new Error('API request failed');
142
- const data = await response.json();
143
- return data.response;
144
- }
145
-
146
- showTypingIndicator() {
147
- const indicator = document.createElement('div');
148
- indicator.className = 'message bot typing-indicator';
149
- indicator.innerHTML = '<div class="typing-dot"></div>'.repeat(3);
150
- document.getElementById('chatMessages').appendChild(indicator);
151
- }
152
-
153
- removeTypingIndicator() {
154
- const indicator = document.querySelector('.typing-indicator');
155
- if (indicator) indicator.remove();
156
  }
157
 
158
- speak(text, onComplete = null) {
159
- this.stopSpeaking();
160
- this.stopListening();
161
-
162
- const chunks = this.splitTextIntoChunks(text, 200);
163
- let currentChunkIndex = 0;
164
- this.isSpeaking = true;
 
165
 
166
- const speakNextChunk = () => {
167
- if (currentChunkIndex >= chunks.length) {
168
- this.isSpeaking = false;
169
- if (this.voiceEnabled) {
170
- setTimeout(() => this.startListening(), 500);
171
- }
172
- if (onComplete) onComplete();
173
- return;
174
  }
 
175
 
176
- const utterance = new SpeechSynthesisUtterance(chunks[currentChunkIndex]);
177
- utterance.rate = 1;
178
- utterance.pitch = 1;
179
-
180
- utterance.onend = () => {
181
- currentChunkIndex++;
182
- if (currentChunkIndex < chunks.length) {
183
- speakNextChunk();
184
- } else {
185
- this.isSpeaking = false;
186
- if (this.voiceEnabled) {
187
- setTimeout(() => this.startListening(), 500);
188
- }
189
- if (onComplete) onComplete();
190
- }
191
- };
192
-
193
- utterance.onerror = (event) => {
194
- console.error('Speech synthesis error:', event.error);
195
- this.isSpeaking = false;
196
- if (onComplete) onComplete();
197
- };
198
-
199
- this.currentUtterance = utterance;
200
- this.synthesis.speak(utterance);
201
  };
202
 
203
- speakNextChunk();
 
 
 
204
  }
 
205
 
206
- splitTextIntoChunks(text, maxLength) {
207
- const chunks = [];
208
- let remainingText = text;
 
 
 
 
209
 
210
- while (remainingText.length > 0) {
211
- if (remainingText.length <= maxLength) {
212
- chunks.push(remainingText);
213
- break;
214
- }
215
 
216
- let chunk = remainingText.slice(0, maxLength);
217
- let lastPeriod = chunk.lastIndexOf('.');
218
- let lastComma = chunk.lastIndexOf(',');
219
- let lastSpace = chunk.lastIndexOf(' ');
 
220
 
221
- let breakPoint = Math.max(
222
- lastPeriod !== -1 ? lastPeriod + 1 : -1,
223
- lastComma !== -1 ? lastComma + 1 : -1,
224
- lastSpace !== -1 ? lastSpace : maxLength
225
- );
226
 
227
- chunk = remainingText.slice(0, breakPoint);
228
- chunks.push(chunk);
229
- remainingText = remainingText.slice(breakPoint).trim();
230
  }
 
 
 
 
 
 
 
 
 
 
 
231
 
232
- return chunks;
 
 
 
 
 
 
 
233
  }
234
 
235
- startListening() {
236
- if (this.isListening || this.isSpeaking) return;
237
-
 
238
  try {
239
- this.isListening = true;
240
- this.autoListening = true;
241
- this.toggleVoiceInputClass(true);
242
- this.recognition.start();
243
- this.startSilenceTimer();
 
 
 
 
 
 
 
 
 
 
244
  } catch (error) {
245
- console.error('Error starting recognition:', error);
246
- this.isListening = false;
247
- this.toggleVoiceInputClass(false);
248
  }
249
  }
250
 
251
- stopListening() {
252
- this.autoListening = false;
253
- this.isListening = false;
254
- this.toggleVoiceInputClass(false);
255
- this.recognition.stop();
256
- this.clearSilenceTimer();
 
 
 
 
 
 
 
 
 
 
 
 
257
  }
258
 
259
  stopSpeaking() {
260
- if (this.isSpeaking || this.currentUtterance) {
261
  this.synthesis.cancel();
262
  this.currentUtterance = null;
263
- this.isSpeaking = false;
 
264
  }
265
  }
266
 
267
- clearInput() {
268
- document.getElementById('messageInput').value = '';
269
- }
270
-
271
- toggleVoiceInputClass(isListening) {
272
  const button = document.getElementById('voiceInput');
273
- button.classList.toggle('listening', isListening);
274
  }
275
 
276
- startSilenceTimer() {
277
- this.clearSilenceTimer();
278
- this.silenceTimeout = setTimeout(() => {
279
- this.stopListening();
280
- }, 10000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  }
282
 
283
- resetSilenceTimer() {
284
- this.clearSilenceTimer();
285
- this.startSilenceTimer();
 
 
286
  }
287
 
288
- clearSilenceTimer() {
289
- if (this.silenceTimeout) {
290
- clearTimeout(this.silenceTimeout);
291
- this.silenceTimeout = null;
292
- }
293
  }
294
  }
295
 
296
  document.addEventListener('DOMContentLoaded', () => {
297
- const chatBot = new ChatBot();
298
  });
 
1
  class ChatBot {
2
  constructor() {
3
+ this.voiceEnabled = false;
4
  this.isListening = false;
 
5
  this.synthesis = window.speechSynthesis;
6
  this.recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
 
7
  this.currentUtterance = null;
8
+ this.isPaused = false;
9
+ this.audioQueue = [];
10
+ this.mediaRecorder = null;
11
+ this.audioChunks = [];
12
+ this.isRecording = false;
13
 
14
  this.setupRecognition();
15
  this.setupEventListeners();
16
  }
17
 
18
  setupRecognition() {
19
+ this.recognition.continuous = true;
20
+ this.recognition.interimResults = true;
21
  this.recognition.lang = 'en-US';
22
 
23
+ this.recognition.onstart = () => {
24
+ this.isListening = true;
25
+ this.toggleVoiceInputClass(true);
 
 
26
  };
27
 
28
  this.recognition.onend = () => {
29
  this.isListening = false;
30
  this.toggleVoiceInputClass(false);
31
+ };
32
+
33
+ this.recognition.onerror = (event) => {
34
+ console.error('Speech recognition error:', event.error);
35
+ this.isListening = false;
36
+ this.toggleVoiceInputClass(false);
37
+ };
38
+
39
+ this.recognition.onresult = (event) => {
40
+ let finalTranscript = '';
41
+ let interimTranscript = '';
42
+
43
+ for (let i = event.resultIndex; i < event.results.length; i++) {
44
+ const transcript = event.results[i][0].transcript;
45
+ if (event.results[i].isFinal) {
46
+ finalTranscript += transcript + ' ';
47
+ } else {
48
+ interimTranscript += transcript;
49
+ }
50
  }
51
+
52
+ const input = document.getElementById('messageInput');
53
+ input.value = finalTranscript + interimTranscript;
54
  };
55
  }
56
 
57
  setupEventListeners() {
58
+ // Send button click event
59
+ document.getElementById('sendMessage').addEventListener('click', () => this.handleSendMessage());
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
+ // Voice input button
62
+ document.getElementById('voiceInput').addEventListener('mousedown', () => {
63
+ this.startRecording();
 
 
 
 
 
64
  });
65
 
66
+ document.getElementById('voiceInput').addEventListener('mouseup', () => {
67
+ this.stopRecording();
 
 
 
 
 
68
  });
69
 
70
+ // Input field key events
71
+ document.getElementById('messageInput').addEventListener('keydown', (e) => {
72
+ if (e.key === 'Enter' && !e.shiftKey) {
73
+ e.preventDefault();
74
+ this.handleSendMessage();
75
  }
76
  });
 
77
 
78
+ // Message speaker button click event
79
+ document.addEventListener('click', (e) => {
80
+ if (e.target.closest('.message-speaker')) {
81
+ const messageContent = e.target.closest('.message-content');
82
+ const text = messageContent.textContent;
83
+ if (this.isPaused) {
84
+ this.resumeSpeaking();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  } else {
86
  this.stopSpeaking();
87
+ this.speak(text);
 
 
 
88
  }
89
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
+ async startRecording() {
94
+ try {
95
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
96
+ this.mediaRecorder = new MediaRecorder(stream, {
97
+ mimeType: 'audio/webm' // Use webm format for better compatibility
98
+ });
99
+ this.audioChunks = [];
100
+ this.isRecording = true;
101
 
102
+ this.mediaRecorder.ondataavailable = (event) => {
103
+ if (event.data.size > 0) {
104
+ this.audioChunks.push(event.data);
 
 
 
 
 
105
  }
106
+ };
107
 
108
+ this.mediaRecorder.onstop = async () => {
109
+ const audioBlob = new Blob(this.audioChunks, { type: 'audio/webm' });
110
+ await this.sendAudioToServer(audioBlob);
111
+ stream.getTracks().forEach(track => track.stop());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  };
113
 
114
+ this.mediaRecorder.start();
115
+ this.toggleVoiceInputClass(true);
116
+ } catch (error) {
117
+ console.error('Error starting recording:', error);
118
  }
119
+ }
120
 
121
+ stopRecording() {
122
+ if (this.mediaRecorder && this.isRecording) {
123
+ this.mediaRecorder.stop();
124
+ this.isRecording = false;
125
+ this.toggleVoiceInputClass(false);
126
+ }
127
+ }
128
 
129
+ async sendAudioToServer(audioBlob) {
130
+ const formData = new FormData();
131
+ formData.append('audio', audioBlob, 'recording.webm'); // Specify filename with extension
 
 
132
 
133
+ try {
134
+ const response = await fetch('/api/voice', {
135
+ method: 'POST',
136
+ body: formData
137
+ });
138
 
139
+ if (!response.ok) throw new Error('Failed to send audio');
 
 
 
 
140
 
141
+ const data = await response.json();
142
+ if (data.text) {
143
+ document.getElementById('messageInput').value = data.text;
144
  }
145
+ if (data.response) {
146
+ this.addMessage(data.response, 'bot');
147
+ if (this.voiceEnabled) {
148
+ this.speak(data.response);
149
+ }
150
+ }
151
+ } catch (error) {
152
+ console.error('Error sending audio:', error);
153
+ this.addMessage('Sorry, there was an error processing your voice input.', 'bot');
154
+ }
155
+ }
156
 
157
+ handleSendMessage() {
158
+ const input = document.getElementById('messageInput');
159
+ const message = input.value.trim();
160
+ if (message) {
161
+ this.stopSpeaking();
162
+ this.sendMessage(message);
163
+ input.value = '';
164
+ }
165
  }
166
 
167
+ async sendMessage(message) {
168
+ this.addMessage(message, 'user');
169
+ this.showTypingIndicator();
170
+
171
  try {
172
+ const response = await fetch('/api/chat', {
173
+ method: 'POST',
174
+ headers: { 'Content-Type': 'application/json' },
175
+ body: JSON.stringify({ message })
176
+ });
177
+
178
+ if (!response.ok) throw new Error('Failed to send message');
179
+
180
+ const data = await response.json();
181
+ this.removeTypingIndicator();
182
+ this.addMessage(data.response, 'bot');
183
+
184
+ if (this.voiceEnabled) {
185
+ this.speak(data.response);
186
+ }
187
  } catch (error) {
188
+ console.error('Error:', error);
189
+ this.removeTypingIndicator();
190
+ this.addMessage('Sorry, there was an error processing your request.', 'bot');
191
  }
192
  }
193
 
194
+ speak(text) {
195
+ if (this.synthesis.speaking) {
196
+ this.synthesis.cancel();
197
+ }
198
+
199
+ const utterance = new SpeechSynthesisUtterance(text);
200
+ this.currentUtterance = utterance;
201
+
202
+ utterance.onend = () => {
203
+ this.currentUtterance = null;
204
+ this.isPaused = false;
205
+ if (this.audioQueue.length > 0) {
206
+ const nextText = this.audioQueue.shift();
207
+ this.speak(nextText);
208
+ }
209
+ };
210
+
211
+ this.synthesis.speak(utterance);
212
  }
213
 
214
  stopSpeaking() {
215
+ if (this.synthesis.speaking) {
216
  this.synthesis.cancel();
217
  this.currentUtterance = null;
218
+ this.isPaused = false;
219
+ this.audioQueue = [];
220
  }
221
  }
222
 
223
+ toggleVoiceInputClass(isActive) {
 
 
 
 
224
  const button = document.getElementById('voiceInput');
225
+ button.classList.toggle('active', isActive);
226
  }
227
 
228
+ addMessage(message, sender) {
229
+ const messagesContainer = document.getElementById('chatMessages');
230
+ const messageDiv = document.createElement('div');
231
+ messageDiv.className = `message ${sender}`;
232
+
233
+ const content = document.createElement('div');
234
+ content.className = 'message-content';
235
+ content.textContent = message;
236
+
237
+ if (sender === 'bot') {
238
+ const speakerButton = document.createElement('button');
239
+ speakerButton.className = 'message-speaker';
240
+ speakerButton.innerHTML = '<i class="fas fa-volume-up"></i>';
241
+ content.appendChild(speakerButton);
242
+ }
243
+
244
+ messageDiv.appendChild(content);
245
+ messagesContainer.appendChild(messageDiv);
246
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
247
  }
248
 
249
+ showTypingIndicator() {
250
+ const indicator = document.createElement('div');
251
+ indicator.className = 'message bot typing-indicator';
252
+ indicator.innerHTML = '<div class="typing-dot"></div>'.repeat(3);
253
+ document.getElementById('chatMessages').appendChild(indicator);
254
  }
255
 
256
+ removeTypingIndicator() {
257
+ const indicator = document.querySelector('.typing-indicator');
258
+ if (indicator) indicator.remove();
 
 
259
  }
260
  }
261
 
262
  document.addEventListener('DOMContentLoaded', () => {
263
+ window.chatBot = new ChatBot();
264
  });