anshharora commited on
Commit
56ad1e0
·
verified ·
1 Parent(s): 62acf57

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +220 -186
static/script.js CHANGED
@@ -1,264 +1,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
  });
 
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
  });