amauricunha commited on
Commit
2e1dae3
·
verified ·
1 Parent(s): 490f79a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +252 -23
index.html CHANGED
@@ -66,12 +66,11 @@
66
  <div id="chatDisplay" class="flex flex-col space-y-2 mb-4"></div>
67
  <div class="flex gap-2">
68
  <input type="text" id="chatInput" class="flex-grow border rounded-lg px-3 py-2" placeholder="Digite ou fale no microfone...">
69
- <button onclick="handleTextInput()" class="btn btn-secondary">Enviar</button>
70
  <button id="micButton" onclick="handleVoiceInput()" class="btn btn-primary mic-button w-12 h-12 rounded-full">🎙️</button>
71
  </div>
72
  </div>
73
 
74
- <!-- NOVO CARD: GERADOR DE IMAGEM -->
75
  <div class="card">
76
  <h2 class="text-xl font-semibold mb-4 text-gray-700">4. Gerador de Imagens (Nano Banana)</h2>
77
  <p class="text-sm text-gray-600 mb-2">Descreva uma cena em inglês para a IA desenhar.</p>
@@ -176,7 +175,8 @@
176
  audioPlayer.play();
177
  audioPlayer.onended = () => URL.revokeObjectURL(audioUrl);
178
  } catch (error) {
179
- alert(`Erro ao gerar áudio: ${error.message}`);
 
180
  } finally {
181
  playButton.disabled = false;
182
  playText.textContent = 'Ouvir Texto';
@@ -185,24 +185,116 @@
185
  }
186
 
187
  // --- LÓGICA DE CONVERSAÇÃO (VOZ E TEXTO) ---
188
- function initializeSpeechRecognition() { /* ... código existente, sem alterações ... */ }
189
- function handleVoiceInput() { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  function handleTextInput() {
191
  const message = chatInput.value.trim();
192
  sendMessage(message);
193
  chatInput.value = '';
194
  }
195
  chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleTextInput(); });
196
-
197
- async function sendMessage(message) { /* ... código existente, sem alterações ... */ }
198
- function appendMessage(text, sender) { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
  // --- LÓGICA DE ESTUDO POR IMAGEM (ANÁLISE) ---
201
- imageUploader.addEventListener('change', (event) => { /* ... código existente, sem alterações ... */ });
202
- async function analyzeImage(base64Image) { /* ... código existente, sem alterações ... */ }
203
- function displayImageVocabulary(vocabulary) { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- // --- LÓGICA DE GERAÇÃO DE IMAGEM (NOVO) ---
206
  async function generateImage() {
207
  const prompt = imagePromptInput.value.trim();
208
  if (!prompt) {
@@ -212,17 +304,13 @@
212
  generateImageBtn.disabled = true;
213
  generateImageBtn.innerHTML = '<div class="spinner mr-2"></div> Gerando...';
214
  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">Criando sua imagem...</p></div>`;
215
-
216
  try {
217
  const response = await fetch('/generate-image', {
218
  method: 'POST',
219
  headers: { 'Content-Type': 'application/json' },
220
  body: JSON.stringify({ prompt: prompt })
221
  });
222
- if (!response.ok) {
223
- const errData = await response.json();
224
- throw new Error(errData.error || 'Erro desconhecido no backend.');
225
- }
226
  const data = await response.json();
227
  imageResultContainer.innerHTML = `<img src="data:image/png;base64,${data.image_base64}" class="rounded-lg max-h-60 mx-auto" alt="Imagem gerada por IA"/>`;
228
  } catch (error) {
@@ -234,16 +322,157 @@
234
  }
235
 
236
  // --- LÓGICA DE GERAÇÃO DE FLASHCARD ---
237
- async function createFlashcardFromSelection() { /* ... código existente, sem alterações ... */ }
238
- function createFlashcardFromSuggestion(term, definition) { /* ... código existente, sem alterações ... */ }
239
- function renderFlashcard(data) { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
  // --- LÓGICA DE PRÁTICA DE PRONÚNCIA ---
242
- async function handlePronunciationPractice(button, targetText, event) { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
  // --- FUNÇÕES AUXILIARES DE ÁUDIO ---
245
- async function fetchAudioBlob(text) { /* ... código existente, sem alterações ... */ }
246
- async function playAudio(text, event) { /* ... código existente, sem alterações ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  // --- INICIALIZAÇÃO ---
249
  document.addEventListener('DOMContentLoaded', () => {
 
66
  <div id="chatDisplay" class="flex flex-col space-y-2 mb-4"></div>
67
  <div class="flex gap-2">
68
  <input type="text" id="chatInput" class="flex-grow border rounded-lg px-3 py-2" placeholder="Digite ou fale no microfone...">
69
+ <button onclick="handleTextInput()" class="btn btn-primary bg-gray-500 hover:bg-gray-600">Enviar</button>
70
  <button id="micButton" onclick="handleVoiceInput()" class="btn btn-primary mic-button w-12 h-12 rounded-full">🎙️</button>
71
  </div>
72
  </div>
73
 
 
74
  <div class="card">
75
  <h2 class="text-xl font-semibold mb-4 text-gray-700">4. Gerador de Imagens (Nano Banana)</h2>
76
  <p class="text-sm text-gray-600 mb-2">Descreva uma cena em inglês para a IA desenhar.</p>
 
175
  audioPlayer.play();
176
  audioPlayer.onended = () => URL.revokeObjectURL(audioUrl);
177
  } catch (error) {
178
+ // MELHORIA: Exibe o erro para o usuário
179
+ alert(`Erro ao gerar áudio: ${error.message}. Verifique o console para mais detalhes.`);
180
  } finally {
181
  playButton.disabled = false;
182
  playText.textContent = 'Ouvir Texto';
 
185
  }
186
 
187
  // --- LÓGICA DE CONVERSAÇÃO (VOZ E TEXTO) ---
188
+ function initializeSpeechRecognition() {
189
+ window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
190
+ if (!window.SpeechRecognition) {
191
+ micButton.disabled = true;
192
+ micButton.textContent = '❌';
193
+ alert('A API de reconhecimento de voz não é suportada neste navegador.');
194
+ return;
195
+ }
196
+ recognition = new SpeechRecognition();
197
+ recognition.lang = 'en-US';
198
+ recognition.interimResults = false;
199
+ recognition.continuous = false;
200
+
201
+ recognition.onresult = (event) => {
202
+ const transcript = event.results[0][0].transcript;
203
+ chatInput.value = transcript;
204
+ sendMessage(transcript);
205
+ };
206
+ recognition.onerror = (event) => { console.error('Erro no reconhecimento de voz:', event.error); };
207
+ recognition.onend = () => {
208
+ isRecording = false;
209
+ micButton.classList.remove('is-recording');
210
+ micButton.innerHTML = '🎙️';
211
+ };
212
+ }
213
+ function handleVoiceInput() {
214
+ if (isRecording) {
215
+ recognition.stop();
216
+ } else {
217
+ isRecording = true;
218
+ micButton.classList.add('is-recording');
219
+ micButton.innerHTML = '<div class="spinner"></div>';
220
+ recognition.start();
221
+ }
222
+ }
223
  function handleTextInput() {
224
  const message = chatInput.value.trim();
225
  sendMessage(message);
226
  chatInput.value = '';
227
  }
228
  chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleTextInput(); });
229
+ async function sendMessage(message) {
230
+ if (!message) return;
231
+ appendMessage(message, 'user');
232
+ chatHistory.push({ role: 'user', content: message });
233
+ try {
234
+ const response = await fetch('/chat-with-ai', {
235
+ method: 'POST',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify({ history: chatHistory, message: message })
238
+ });
239
+ if (!response.ok) throw new Error((await response.json()).error);
240
+ const data = await response.json();
241
+ appendMessage(data.response, 'ai');
242
+ chatHistory.push({ role: 'assistant', content: data.response });
243
+ playAudio(data.response);
244
+ } catch (error) {
245
+ appendMessage(`Erro na conversa: ${error.message}`, 'ai');
246
+ }
247
+ }
248
+ function appendMessage(text, sender) {
249
+ const msg = document.createElement('div');
250
+ msg.className = `chat-message ${sender}-message`;
251
+ msg.textContent = text;
252
+ chatDisplay.appendChild(msg);
253
+ chatDisplay.scrollTop = chatDisplay.scrollHeight;
254
+ }
255
 
256
  // --- LÓGICA DE ESTUDO POR IMAGEM (ANÁLISE) ---
257
+ imageUploader.addEventListener('change', (event) => {
258
+ const file = event.target.files[0];
259
+ if (!file) return;
260
+ const reader = new FileReader();
261
+ reader.onload = (e) => {
262
+ imagePreview.src = e.target.result;
263
+ imagePreview.classList.remove('hidden');
264
+ analyzeImage(e.target.result);
265
+ };
266
+ reader.readAsDataURL(file);
267
+ });
268
+ async function analyzeImage(base64Image) {
269
+ 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">Analisando imagem...</p></div>`;
270
+ try {
271
+ const response = await fetch('/analyze-image', {
272
+ method: 'POST',
273
+ headers: { 'Content-Type': 'application/json' },
274
+ body: JSON.stringify({ image: base64Image })
275
+ });
276
+ if (!response.ok) throw new Error((await response.json()).error);
277
+ const vocabulary = await response.json();
278
+ displayImageVocabulary(vocabulary);
279
+ } catch (error) {
280
+ imageAnalysisResult.innerHTML = `<p class="text-red-600 font-semibold">Falha na análise: ${error.message}</p>`;
281
+ }
282
+ }
283
+ function displayImageVocabulary(vocabulary) {
284
+ imageAnalysisResult.innerHTML = '<h3 class="font-semibold mb-2">Vocabulário Sugerido:</h3>';
285
+ const buttonContainer = document.createElement('div');
286
+ buttonContainer.className = 'flex flex-wrap gap-2';
287
+ vocabulary.forEach(({ term, definition }) => {
288
+ const btn = document.createElement('button');
289
+ btn.className = 'btn bg-violet-100 text-violet-700 hover:bg-violet-200 text-sm';
290
+ btn.textContent = term;
291
+ btn.onclick = () => createFlashcardFromSuggestion(term, definition);
292
+ buttonContainer.appendChild(btn);
293
+ });
294
+ imageAnalysisResult.appendChild(buttonContainer);
295
+ }
296
 
297
+ // --- LÓGICA DE GERAÇÃO DE IMAGEM (NANO BANANA) ---
298
  async function generateImage() {
299
  const prompt = imagePromptInput.value.trim();
300
  if (!prompt) {
 
304
  generateImageBtn.disabled = true;
305
  generateImageBtn.innerHTML = '<div class="spinner mr-2"></div> Gerando...';
306
  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">Criando sua imagem...</p></div>`;
 
307
  try {
308
  const response = await fetch('/generate-image', {
309
  method: 'POST',
310
  headers: { 'Content-Type': 'application/json' },
311
  body: JSON.stringify({ prompt: prompt })
312
  });
313
+ if (!response.ok) throw new Error((await response.json()).error);
 
 
 
314
  const data = await response.json();
315
  imageResultContainer.innerHTML = `<img src="data:image/png;base64,${data.image_base64}" class="rounded-lg max-h-60 mx-auto" alt="Imagem gerada por IA"/>`;
316
  } catch (error) {
 
322
  }
323
 
324
  // --- LÓGICA DE GERAÇÃO DE FLASHCARD ---
325
+ async function createFlashcard(word, context, definition = null) {
326
+ const btn = definition ? null : createCardButton;
327
+ if (btn) {
328
+ btn.disabled = true;
329
+ btn.innerHTML = '<div class="spinner mr-2"></div> Criando...';
330
+ }
331
+ try {
332
+ const response = await fetch('/explain-proxy', {
333
+ method: 'POST',
334
+ headers: { 'Content-Type': 'application/json' },
335
+ body: JSON.stringify({
336
+ word: word,
337
+ context: context,
338
+ for_flashcard: true,
339
+ model: modelSelector.value,
340
+ context_focus: contextSelector.value
341
+ })
342
+ });
343
+ if (!response.ok) {
344
+ const errData = await response.json();
345
+ throw new Error(errData.error || 'Erro desconhecido do servidor.');
346
+ }
347
+ const cardData = await response.json();
348
+ if(definition && !cardData.definition) cardData.definition = definition; // Usa a definição da análise de imagem se a IA não retornar uma
349
+ renderFlashcard(cardData);
350
+ } catch (error) {
351
+ // MELHORIA: Exibe o erro para o usuário
352
+ alert(`Falha ao criar flashcard: ${error.message}. Verifique as chaves de API e a seleção do modelo.`);
353
+ } finally {
354
+ if (btn) {
355
+ btn.disabled = false;
356
+ btn.innerHTML = '+ Criar Flashcard';
357
+ }
358
+ }
359
+ }
360
+ function createFlashcardFromSelection() {
361
+ const selection = window.getSelection().toString().trim();
362
+ if (selection) createFlashcard(selection, textEditor.innerText);
363
+ }
364
+ function createFlashcardFromSuggestion(term, definition) {
365
+ createFlashcard(term, `(From image analysis) A visual representation of ${term}.`, definition);
366
+ }
367
+ function renderFlashcard(data) {
368
+ noCardsMessage.classList.add('hidden');
369
+ const cardId = `card-${Date.now()}`;
370
+ const container = document.createElement('div');
371
+ container.className = 'flashcard-container';
372
+ container.id = cardId;
373
+ const minHeight = 380;
374
+ container.style.minHeight = `${minHeight}px`;
375
+
376
+ container.innerHTML = `
377
+ <div class="flashcard-inner" style="min-height: ${minHeight}px;">
378
+ <div class="flashcard-front">
379
+ <div>
380
+ <div class="text-sm text-gray-600 mb-2">Frase de Contexto:</div>
381
+ <p class="text-lg text-center text-gray-800">${data.gapped_sentence || 'N/A'}</p>
382
+ </div>
383
+ <div class="border-t pt-4 mt-4 text-center">
384
+ <strong class="text-indigo-600">Dica (PT):</strong> ${data.translation || 'N/A'}
385
+ </div>
386
+ <div class="text-xs text-center text-gray-400 mt-4">Clique para ver a resposta</div>
387
+ </div>
388
+ <div class="flashcard-back">
389
+ <div>
390
+ <h3 class="text-xl font-bold text-center text-indigo-600 mb-2">${data.term}</h3>
391
+ <p class="flex items-center justify-center gap-2 text-gray-600 italic mb-3">
392
+ <button class="audio-btn" onclick="playAudio('${(data.context_sentence || '').replace(/'/g, "\\'")}', event)">🔊</button>
393
+ <span>${data.context_sentence || 'N/A'}</span>
394
+ </p>
395
+ <p class="text-sm text-gray-800"><strong class="font-semibold">Definição:</strong> ${data.definition || 'N/A'}</p>
396
+ </div>
397
+ <div class="border-t mt-3 pt-3">
398
+ <h4 class="text-sm font-semibold text-center">Pratique sua Pronúncia</h4>
399
+ <div class="flex justify-center items-center gap-2 mt-2">
400
+ <button class="btn btn-primary w-12 h-12 rounded-full" onclick="handlePronunciationPractice(this, '${(data.context_sentence || '').replace(/'/g, "\\'")}', event)">🎙️</button>
401
+ </div>
402
+ <div class="text-xs text-gray-600 p-2 mt-2 bg-gray-50 rounded-md min-h-[40px]" data-feedback-area></div>
403
+ </div>
404
+ </div>
405
+ </div>`;
406
+
407
+ flashcardList.prepend(container);
408
+ container.addEventListener('click', (e) => {
409
+ if(e.target.tagName !== 'BUTTON') container.classList.toggle('flipped');
410
+ });
411
+ }
412
 
413
  // --- LÓGICA DE PRÁTICA DE PRONÚNCIA ---
414
+ async function handlePronunciationPractice(button, targetText, event) {
415
+ event.stopPropagation();
416
+ const feedbackArea = button.closest('.flashcard-back').querySelector('[data-feedback-area]');
417
+
418
+ const practiceRecognition = new SpeechRecognition();
419
+ practiceRecognition.lang = 'en-US';
420
+
421
+ button.innerHTML = '<div class="spinner"></div>';
422
+ button.disabled = true;
423
+ feedbackArea.textContent = 'Ouvindo...';
424
+
425
+ practiceRecognition.start();
426
+
427
+ practiceRecognition.onresult = async (e) => {
428
+ const userText = e.results[0][0].transcript;
429
+ feedbackArea.textContent = `Você disse: "${userText}". Analisando...`;
430
+ try {
431
+ const response = await fetch('/pronunciation-feedback', {
432
+ method: 'POST',
433
+ headers: { 'Content-Type': 'application/json' },
434
+ body: JSON.stringify({ target_text: targetText, user_text: userText })
435
+ });
436
+ if (!response.ok) throw new Error((await response.json()).error);
437
+ const data = await response.json();
438
+ feedbackArea.innerHTML = `<strong>Feedback:</strong> ${data.feedback}`;
439
+ } catch (error) {
440
+ feedbackArea.textContent = `Erro na análise: ${error.message}`;
441
+ } finally {
442
+ button.innerHTML = '🎙️';
443
+ button.disabled = false;
444
+ }
445
+ };
446
+ practiceRecognition.onerror = (e) => {
447
+ feedbackArea.textContent = `Erro ao gravar: ${e.error}`;
448
+ button.innerHTML = '🎙️';
449
+ button.disabled = false;
450
+ };
451
+ }
452
 
453
  // --- FUNÇÕES AUXILIARES DE ÁUDIO ---
454
+ async function fetchAudioBlob(text) {
455
+ const response = await fetch('/tts-proxy', {
456
+ method: 'POST',
457
+ headers: { 'Content-Type': 'application/json' },
458
+ body: JSON.stringify({ text: text })
459
+ });
460
+ if (!response.ok) {
461
+ const err = await response.json();
462
+ throw new Error(err.error || `HTTP error! status: ${response.status}`);
463
+ }
464
+ return await response.blob();
465
+ }
466
+ async function playAudio(text, event) {
467
+ if(event) event.stopPropagation();
468
+ try {
469
+ const audioBlob = await fetchAudioBlob(text);
470
+ const audioUrl = URL.createObjectURL(audioBlob);
471
+ const audio = new Audio(audioUrl);
472
+ audio.play();
473
+ audio.onended = () => URL.revokeObjectURL(audioUrl);
474
+ } catch (error) { console.error('Erro ao tocar áudio:', error); }
475
+ }
476
 
477
  // --- INICIALIZAÇÃO ---
478
  document.addEventListener('DOMContentLoaded', () => {