GamerC0der commited on
Commit
afc5457
·
verified ·
1 Parent(s): 93e926b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -3
app.py CHANGED
@@ -32,7 +32,7 @@ MAIN_HTML = """
32
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
33
  </head>
34
  <body>
35
- <h1>Multi-Modal Playground (No JS)</h1>
36
 
37
  <h2>STT (Nova-3)</h2>
38
  <form action="/stt" method="post" enctype="multipart/form-data">
@@ -55,12 +55,125 @@ MAIN_HTML = """
55
  <button type="submit">Send</button>
56
  </form>
57
 
 
 
 
58
  <hr>
59
  <a href="/">Refresh Playground</a>
60
  </body>
61
  </html>
62
  """
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  STT_RESULT_HTML = """
65
  <!DOCTYPE html>
66
  <html>
@@ -110,12 +223,16 @@ class Handler(http.server.BaseHTTPRequestHandler):
110
  self.send_header("Content-type", "text/html")
111
  self.end_headers()
112
  self.wfile.write(MAIN_HTML.encode())
 
 
 
 
 
113
  else:
114
  self.send_error(404)
115
 
116
  def do_POST(self):
117
  if self.path == '/api/stt':
118
- # Keep old API endpoint for compatibility (though not used in JS-less)
119
  content_length = int(self.headers['Content-Length'])
120
  body = self.rfile.read(content_length)
121
  r = requests.post(STT_URL, data=body, impersonate="chrome")
@@ -200,7 +317,6 @@ class Handler(http.server.BaseHTTPRequestHandler):
200
  return
201
  self.send_error(400, "No message provided")
202
 
203
- # Keep old /api/tts and /api/chat for compatibility (though not used)
204
  elif self.path == '/api/tts':
205
  content_length = int(self.headers['Content-Length'])
206
  body_str = self.rfile.read(content_length).decode('utf-8')
 
32
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
33
  </head>
34
  <body>
35
+ <h1>Multi-Modal Playground</h1>
36
 
37
  <h2>STT (Nova-3)</h2>
38
  <form action="/stt" method="post" enctype="multipart/form-data">
 
55
  <button type="submit">Send</button>
56
  </form>
57
 
58
+ <h2>Voice Chat</h2>
59
+ <p><a href="/voicechat">Go to Voice Chat</a></p>
60
+
61
  <hr>
62
  <a href="/">Refresh Playground</a>
63
  </body>
64
  </html>
65
  """
66
 
67
+ VOICECHAT_HTML = """
68
+ <!DOCTYPE html>
69
+ <html>
70
+ <head>
71
+ <title>Voice Chat</title>
72
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
73
+ </head>
74
+ <body>
75
+ <h1>Voice Chat</h1>
76
+ <div id="messages" style="height:300px;overflow-y:scroll;border:1px solid #ccc;padding:10px;margin-bottom:10px;background:#eee;"></div>
77
+ <button id="micBtn" onclick="toggleRecord()" style="font-size:48px;"><i class="fas fa-microphone"></i></button>
78
+ <p>Status: <span id="statusVoice">Click to start recording</span></p>
79
+ <audio id="voicePlayer" style="display:none;"></audio>
80
+ <p><a href="/">Back to Playground</a></p>
81
+
82
+ <script>
83
+ let chatMessages = [];
84
+ let mediaRecorder;
85
+ let audioChunks = [];
86
+ let voiceStream;
87
+
88
+ function renderMD(text) {
89
+ return text.replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')
90
+ .replace(/\*(.*?)\*/g, '<i>$1</i>')
91
+ .replace(/`(.*?)`/g, '<code>$1</code>')
92
+ .replace(/\n/g, '<br>');
93
+ }
94
+
95
+ function addMessage(role, content) {
96
+ const div = document.getElementById('messages');
97
+ const msg = document.createElement('div');
98
+ msg.innerHTML = `<strong>${role}:</strong> ${renderMD(content)}`;
99
+ div.appendChild(msg);
100
+ div.scrollTop = div.scrollHeight;
101
+ }
102
+
103
+ async function toggleRecord() {
104
+ const btn = document.getElementById('micBtn');
105
+ if (!mediaRecorder || mediaRecorder.state === 'inactive') {
106
+ try {
107
+ voiceStream = await navigator.mediaDevices.getUserMedia({audio: true});
108
+ mediaRecorder = new MediaRecorder(voiceStream);
109
+ audioChunks = [];
110
+ mediaRecorder.ondataavailable = e => audioChunks.push(e.data);
111
+ mediaRecorder.onstop = processVoice;
112
+ mediaRecorder.start();
113
+ btn.style.color = 'red';
114
+ document.getElementById('statusVoice').innerText = 'Recording... Click to stop';
115
+ } catch (e) {
116
+ console.error('Mic error:', e);
117
+ document.getElementById('statusVoice').innerText = 'Error accessing mic';
118
+ }
119
+ } else {
120
+ mediaRecorder.stop();
121
+ btn.style.color = 'black';
122
+ document.getElementById('statusVoice').innerText = 'Processing...';
123
+ }
124
+ }
125
+
126
+ async function processVoice() {
127
+ const audioBlob = new Blob(audioChunks, {type: 'audio/webm'});
128
+ if (voiceStream) {
129
+ voiceStream.getTracks().forEach(track => track.stop());
130
+ }
131
+ document.getElementById('statusVoice').innerText = 'Transcribing...';
132
+ try {
133
+ const sttRes = await fetch('/api/stt', {method: 'POST', body: audioBlob});
134
+ const sttData = await sttRes.json();
135
+ let userText = '';
136
+ if (sttData.results && sttData.results.channels && sttData.results.channels[0] &&
137
+ sttData.results.channels[0].alternatives && sttData.results.channels[0].alternatives[0]) {
138
+ userText = sttData.results.channels[0].alternatives[0].transcript;
139
+ }
140
+ if (!userText) {
141
+ document.getElementById('statusVoice').innerText = 'No speech detected';
142
+ return;
143
+ }
144
+ addMessage('user', userText);
145
+ chatMessages.push({role: 'user', content: userText});
146
+ document.getElementById('statusVoice').innerText = 'Thinking...';
147
+ const chatRes = await fetch('/api/chat', {
148
+ method: 'POST',
149
+ headers: {'Content-Type': 'application/json'},
150
+ body: JSON.stringify({messages: chatMessages})
151
+ });
152
+ const chatData = await chatRes.json();
153
+ const response = chatData.response;
154
+ addMessage('assistant', response);
155
+ chatMessages.push({role: 'assistant', content: response});
156
+ document.getElementById('statusVoice').innerText = 'Generating speech...';
157
+ const ttsRes = await fetch('/api/tts', {
158
+ method: 'POST',
159
+ headers: {'Content-Type': 'application/json'},
160
+ body: JSON.stringify({text: response})
161
+ });
162
+ const ttsData = await ttsRes.json();
163
+ const audioPlayer = document.getElementById('voicePlayer');
164
+ audioPlayer.src = 'data:audio/webm;base64,' + ttsData.audio;
165
+ audioPlayer.play();
166
+ document.getElementById('statusVoice').innerText = 'Done';
167
+ } catch (e) {
168
+ console.error('Voice process error:', e);
169
+ document.getElementById('statusVoice').innerText = 'Error';
170
+ }
171
+ }
172
+ </script>
173
+ </body>
174
+ </html>
175
+ """
176
+
177
  STT_RESULT_HTML = """
178
  <!DOCTYPE html>
179
  <html>
 
223
  self.send_header("Content-type", "text/html")
224
  self.end_headers()
225
  self.wfile.write(MAIN_HTML.encode())
226
+ elif self.path.split('?')[0] == '/voicechat':
227
+ self.send_response(200)
228
+ self.send_header("Content-type", "text/html")
229
+ self.end_headers()
230
+ self.wfile.write(VOICECHAT_HTML.encode())
231
  else:
232
  self.send_error(404)
233
 
234
  def do_POST(self):
235
  if self.path == '/api/stt':
 
236
  content_length = int(self.headers['Content-Length'])
237
  body = self.rfile.read(content_length)
238
  r = requests.post(STT_URL, data=body, impersonate="chrome")
 
317
  return
318
  self.send_error(400, "No message provided")
319
 
 
320
  elif self.path == '/api/tts':
321
  content_length = int(self.headers['Content-Length'])
322
  body_str = self.rfile.read(content_length).decode('utf-8')