amauricunha commited on
Commit
4bdb212
·
verified ·
1 Parent(s): 663c63a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +56 -192
index.html CHANGED
@@ -42,10 +42,9 @@
42
 
43
  <div class="card mb-8">
44
  <h2 class="text-xl font-semibold mb-4 text-gray-700">Global Session Settings</h2>
45
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
46
  <div>
47
  <label for="modelSelector" class="block text-sm font-medium text-gray-700">Main AI Model</label>
48
- <!-- O CONTEÚDO DESTE SELECT SERÁ PREENCHIDO DINAMICAMENTE -->
49
  <select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
50
  <option>Loading models...</option>
51
  </select>
@@ -58,6 +57,14 @@
58
  <option value="Technical/IT">Technical/IT</option>
59
  </select>
60
  </div>
 
 
 
 
 
 
 
 
61
  </div>
62
  </div>
63
 
@@ -151,6 +158,7 @@
151
  // --- ELEMENTOS GLOBAIS ---
152
  const modelSelector = document.getElementById('modelSelector');
153
  const contextSelector = document.getElementById('contextSelector');
 
154
  const textEditor = document.getElementById('textEditor');
155
  const createCardButton = document.getElementById('createCardButton');
156
  const playButton = document.getElementById('playButton');
@@ -184,14 +192,11 @@
184
  const response = await fetch('/list-models');
185
  if (!response.ok) throw new Error('Failed to fetch model list.');
186
  const models = await response.json();
187
-
188
- modelSelector.innerHTML = ''; // Limpa o "Loading..."
189
-
190
  const geminiGroup = document.createElement('optgroup');
191
  geminiGroup.label = 'Google Gemini';
192
  const groqGroup = document.createElement('optgroup');
193
  groqGroup.label = 'Groq (Ultra Fast)';
194
-
195
  models.forEach(model => {
196
  const option = document.createElement('option');
197
  option.value = model.value;
@@ -202,21 +207,40 @@
202
  groqGroup.appendChild(option);
203
  }
204
  });
205
-
206
  modelSelector.appendChild(geminiGroup);
207
  modelSelector.appendChild(groqGroup);
208
-
209
  } catch (error) {
210
  console.error("Error populating models:", error);
211
  modelSelector.innerHTML = '<option>Error loading models</option>';
212
  }
213
  }
214
 
215
- // --- LÓGICA DE ATIVIDADES DE ESCRITA ---
216
- async function generateActivity() { /* ... (código existente sem alterações) ... */ }
217
- async function submitActivityResponse() { /* ... (código existente sem alterações) ... */ }
218
-
219
- // --- OUTRAS FUNÇÕES ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  const charCount = document.getElementById('charCount');
221
  const playText = document.getElementById('playText');
222
  const playSpinner = document.getElementById('playSpinner');
@@ -249,185 +273,25 @@
249
  playSpinner.classList.add('hidden');
250
  }
251
  }
252
- function initializeSpeechRecognition() {
253
- window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
254
- if (!window.SpeechRecognition) {
255
- micButton.disabled = true;
256
- micButton.textContent = '❌';
257
- alert('Voice recognition API is not supported in this browser.');
258
- return;
259
- }
260
- recognition = new SpeechRecognition();
261
- recognition.lang = 'en-US';
262
- recognition.interimResults = false;
263
- recognition.continuous = false;
264
- recognition.onresult = (event) => {
265
- const transcript = event.results[0][0].transcript;
266
- chatInput.value = transcript;
267
- sendMessage(transcript);
268
- };
269
- recognition.onerror = (event) => { console.error('Voice recognition error:', event.error); };
270
- recognition.onend = () => {
271
- isRecording = false;
272
- micButton.classList.remove('is-recording');
273
- micButton.innerHTML = '🎙️';
274
- };
275
- }
276
- function handleVoiceInput() {
277
- if (isRecording) {
278
- recognition.stop();
279
- } else {
280
- isRecording = true;
281
- micButton.classList.add('is-recording');
282
- micButton.innerHTML = '<div class="spinner"></div>';
283
- recognition.start();
284
- }
285
- }
286
- function handleTextInput() {
287
- const message = chatInput.value.trim();
288
- sendMessage(message);
289
- chatInput.value = '';
290
- }
291
- chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleTextInput(); });
292
- async function sendMessage(message) {
293
- if (!message) return;
294
- appendMessage(message, 'user');
295
- chatHistory.push({ role: 'user', content: message });
296
- try {
297
- const response = await fetch('/chat-with-ai', {
298
- method: 'POST',
299
- headers: { 'Content-Type': 'application/json' },
300
- body: JSON.stringify({ history: chatHistory, message: message })
301
- });
302
- if (!response.ok) throw new Error((await response.json()).error);
303
- const data = await response.json();
304
- appendMessage(data.response, 'ai');
305
- chatHistory.push({ role: 'assistant', content: data.response });
306
- playAudio(data.response);
307
- } catch (error) {
308
- appendMessage(`Chat error: ${error.message}`, 'ai');
309
- }
310
- }
311
- function appendMessage(text, sender) {
312
- const msg = document.createElement('div');
313
- msg.className = `chat-message ${sender}-message`;
314
- msg.textContent = text;
315
- chatDisplay.appendChild(msg);
316
- chatDisplay.scrollTop = chatDisplay.scrollHeight;
317
- }
318
- imageUploader.addEventListener('change', (event) => {
319
- const file = event.target.files[0];
320
- if (!file) return;
321
- const reader = new FileReader();
322
- reader.onload = (e) => {
323
- imagePreview.src = e.target.result;
324
- imagePreview.classList.remove('hidden');
325
- analyzeImage(e.target.result);
326
- };
327
- reader.readAsDataURL(file);
328
- });
329
- async function analyzeImage(base64Image) {
330
- imageAnalysisResult.innerHTML = `<div class="flex items-center p-2"><div class="spinner !border-l-indigo-500 !border-gray-200"></div><p class="ml-3 text-gray-600">Analyzing image...</p></div>`;
331
- try {
332
- const response = await fetch('/analyze-image', {
333
- method: 'POST',
334
- headers: { 'Content-Type': 'application/json' },
335
- body: JSON.stringify({
336
- image: base64Image,
337
- model: modelSelector.value
338
- })
339
- });
340
- if (!response.ok) throw new Error((await response.json()).error);
341
- const vocabulary = await response.json();
342
- displayImageVocabulary(vocabulary);
343
- } catch (error) {
344
- imageAnalysisResult.innerHTML = `<p class="text-red-600 font-semibold">Analysis failed: ${error.message}</p>`;
345
- }
346
- }
347
- function displayImageVocabulary(vocabulary) {
348
- imageAnalysisResult.innerHTML = '<h3 class="font-semibold mb-2">Suggested Vocabulary:</h3>';
349
- const buttonContainer = document.createElement('div');
350
- buttonContainer.className = 'flex flex-wrap gap-2';
351
- vocabulary.forEach(({ term, definition }) => {
352
- const btn = document.createElement('button');
353
- btn.className = 'btn bg-violet-100 text-violet-700 hover:bg-violet-200 text-sm';
354
- btn.textContent = term;
355
- btn.onclick = () => createFlashcardFromSuggestion(term, definition);
356
- buttonContainer.appendChild(btn);
357
- });
358
- imageAnalysisResult.appendChild(buttonContainer);
359
- }
360
- async function generateImage() {
361
- const prompt = imagePromptInput.value.trim();
362
- if (!prompt) {
363
- alert('Please enter a description for the image.');
364
- return;
365
- }
366
- generateImageBtn.disabled = true;
367
- generateImageBtn.innerHTML = '<div class="spinner mr-2"></div> Generating...';
368
- imageResultContainer.innerHTML = `<div class="flex items-center justify-center p-4"><div class="spinner !border-l-indigo-500 !border-gray-200"></div><p class="ml-3 text-gray-600">Creating your image...</p></div>`;
369
- try {
370
- const response = await fetch('/generate-image', {
371
- method: 'POST',
372
- headers: { 'Content-Type': 'application/json' },
373
- body: JSON.stringify({ prompt: prompt })
374
- });
375
- if (!response.ok) throw new Error((await response.json()).error);
376
- const data = await response.json();
377
- imageResultContainer.innerHTML = `<img src="data:image/png;base64,${data.image_base64}" class="rounded-lg max-h-60 mx-auto" alt="AI-generated image"/>`;
378
- } catch (error) {
379
- imageResultContainer.innerHTML = `<p class="text-red-600 font-semibold">Failed to generate image: ${error.message}</p>`;
380
- } finally {
381
- generateImageBtn.disabled = false;
382
- generateImageBtn.textContent = 'Generate';
383
- }
384
- }
385
- async function createFlashcard(word, context, definition = null) {
386
- const btn = definition ? null : createCardButton;
387
- if (btn) {
388
- btn.disabled = true;
389
- btn.innerHTML = '<div class="spinner mr-2"></div> Creating...';
390
- }
391
- try {
392
- const response = await fetch('/explain-proxy', {
393
- method: 'POST',
394
- headers: { 'Content-Type': 'application/json' },
395
- body: JSON.stringify({
396
- word: word,
397
- context: context,
398
- for_flashcard: true,
399
- model: modelSelector.value,
400
- context_focus: contextSelector.value
401
- })
402
- });
403
- if (!response.ok) {
404
- const errData = await response.json();
405
- throw new Error(errData.error || 'Unknown server error.');
406
- }
407
- const cardData = await response.json();
408
- if(definition && !cardData.definition) cardData.definition = definition;
409
- renderFlashcard(cardData);
410
- } catch (error) {
411
- alert(`Failed to create flashcard: ${error.message}. Check API keys and model selection.`);
412
- } finally {
413
- if (btn) {
414
- btn.disabled = false;
415
- btn.innerHTML = '+ Create Flashcard';
416
- }
417
- }
418
- }
419
- function createFlashcardFromSelection() {
420
- const selection = window.getSelection().toString().trim();
421
- if (selection) createFlashcard(selection, textEditor.innerText);
422
- }
423
- function createFlashcardFromSuggestion(term, definition) {
424
- createFlashcard(term, `(From image analysis) A visual representation of ${term}.`, definition);
425
- }
426
- function renderFlashcard(data) { /* ... (código existente sem alterações) ... */ }
427
- function deleteFlashcard(cardId, event) { /* ... (código existente sem alterações) ... */ }
428
- async function handlePronunciationPractice(button, targetText, event) { /* ... (código existente sem alterações) ... */ }
429
- async function fetchAudioBlob(text) { /* ... (código existente sem alterações) ... */ }
430
- async function playAudio(text, event) { /* ... (código existente sem alterações) ... */ }
431
 
432
  // --- INICIALIZAÇÃO ---
433
  document.addEventListener('DOMContentLoaded', () => {
 
42
 
43
  <div class="card mb-8">
44
  <h2 class="text-xl font-semibold mb-4 text-gray-700">Global Session Settings</h2>
45
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
46
  <div>
47
  <label for="modelSelector" class="block text-sm font-medium text-gray-700">Main AI Model</label>
 
48
  <select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
49
  <option>Loading models...</option>
50
  </select>
 
57
  <option value="Technical/IT">Technical/IT</option>
58
  </select>
59
  </div>
60
+ <div>
61
+ <label for="voiceSelector" class="block text-sm font-medium text-gray-700">Voice Accent (TTS)</label>
62
+ <select id="voiceSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
63
+ <option value="co.uk">British (Female)</option>
64
+ <option value="com">American (Female)</option>
65
+ <option value="com.au">Australian (Female)</option>
66
+ </select>
67
+ </div>
68
  </div>
69
  </div>
70
 
 
158
  // --- ELEMENTOS GLOBAIS ---
159
  const modelSelector = document.getElementById('modelSelector');
160
  const contextSelector = document.getElementById('contextSelector');
161
+ const voiceSelector = document.getElementById('voiceSelector');
162
  const textEditor = document.getElementById('textEditor');
163
  const createCardButton = document.getElementById('createCardButton');
164
  const playButton = document.getElementById('playButton');
 
192
  const response = await fetch('/list-models');
193
  if (!response.ok) throw new Error('Failed to fetch model list.');
194
  const models = await response.json();
195
+ modelSelector.innerHTML = '';
 
 
196
  const geminiGroup = document.createElement('optgroup');
197
  geminiGroup.label = 'Google Gemini';
198
  const groqGroup = document.createElement('optgroup');
199
  groqGroup.label = 'Groq (Ultra Fast)';
 
200
  models.forEach(model => {
201
  const option = document.createElement('option');
202
  option.value = model.value;
 
207
  groqGroup.appendChild(option);
208
  }
209
  });
 
210
  modelSelector.appendChild(geminiGroup);
211
  modelSelector.appendChild(groqGroup);
 
212
  } catch (error) {
213
  console.error("Error populating models:", error);
214
  modelSelector.innerHTML = '<option>Error loading models</option>';
215
  }
216
  }
217
 
218
+ // --- FUNÇÕES DE ÁUDIO ---
219
+ async function fetchAudioBlob(text) {
220
+ const selectedTld = voiceSelector.value;
221
+ const response = await fetch('/tts-proxy', {
222
+ method: 'POST',
223
+ headers: { 'Content-Type': 'application/json' },
224
+ body: JSON.stringify({ text: text, tld: selectedTld })
225
+ });
226
+ if (!response.ok) {
227
+ const err = await response.json();
228
+ throw new Error(err.error || `HTTP error! status: ${response.status}`);
229
+ }
230
+ return await response.blob();
231
+ }
232
+ async function playAudio(text, event) {
233
+ if(event) event.stopPropagation();
234
+ try {
235
+ const audioBlob = await fetchAudioBlob(text);
236
+ const audioUrl = URL.createObjectURL(audioBlob);
237
+ const audio = new Audio(audioUrl);
238
+ audio.play();
239
+ audio.onended = () => URL.revokeObjectURL(audioUrl);
240
+ } catch (error) { console.error('Error playing audio:', error); }
241
+ }
242
+
243
+ // --- LÓGICA DO EDITOR ---
244
  const charCount = document.getElementById('charCount');
245
  const playText = document.getElementById('playText');
246
  const playSpinner = document.getElementById('playSpinner');
 
273
  playSpinner.classList.add('hidden');
274
  }
275
  }
276
+
277
+ // --- TODAS AS OUTRAS FUNÇÕES (sem alterações) ---
278
+ async function generateActivity() { /* ... */ }
279
+ async function submitActivityResponse() { /* ... */ }
280
+ function initializeSpeechRecognition() { /* ... */ }
281
+ function handleVoiceInput() { /* ... */ }
282
+ function handleTextInput() { /* ... */ }
283
+ async function sendMessage(message) { /* ... */ }
284
+ function appendMessage(text, sender) { /* ... */ }
285
+ imageUploader.addEventListener('change', (event) => { /* ... */ });
286
+ async function analyzeImage(base64Image) { /* ... */ }
287
+ function displayImageVocabulary(vocabulary) { /* ... */ }
288
+ async function generateImage() { /* ... */ }
289
+ async function createFlashcard(word, context, definition = null) { /* ... */ }
290
+ function createFlashcardFromSelection() { /* ... */ }
291
+ function createFlashcardFromSuggestion(term, definition) { /* ... */ }
292
+ function renderFlashcard(data) { /* ... */ }
293
+ function deleteFlashcard(cardId, event) { /* ... */ }
294
+ async function handlePronunciationPractice(button, targetText, event) { /* ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
  // --- INICIALIZAÇÃO ---
297
  document.addEventListener('DOMContentLoaded', () => {