amauricunha commited on
Commit
006bd9f
·
verified ·
1 Parent(s): 727cd3e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +274 -16
index.html CHANGED
@@ -274,7 +274,9 @@
274
  }
275
  }
276
 
277
- // --- TODAS AS OUTRAS FUNÇÕES (sem alterações) ---
 
 
278
  async function generateActivity() {
279
  const activityType = activityTypeSelector.value;
280
  const context = contextSelector.value;
@@ -366,21 +368,277 @@
366
  submitActivityBtn.textContent = 'Submit Answer';
367
  }
368
  }
369
- function initializeSpeechRecognition() { /* ... */ }
370
- function handleVoiceInput() { /* ... */ }
371
- function handleTextInput() { /* ... */ }
372
- async function sendMessage(message) { /* ... */ }
373
- function appendMessage(text, sender) { /* ... */ }
374
- imageUploader.addEventListener('change', (event) => { /* ... */ });
375
- async function analyzeImage(base64Image) { /* ... */ }
376
- function displayImageVocabulary(vocabulary) { /* ... */ }
377
- async function generateImage() { /* ... */ }
378
- async function createFlashcard(word, context, definition = null) { /* ... */ }
379
- function createFlashcardFromSelection() { /* ... */ }
380
- function createFlashcardFromSuggestion(term, definition) { /* ... */ }
381
- function renderFlashcard(data) { /* ... */ }
382
- function deleteFlashcard(cardId, event) { /* ... */ }
383
- async function handlePronunciationPractice(button, targetText, event) { /* ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
  // --- INICIALIZAÇÃO ---
386
  document.addEventListener('DOMContentLoaded', () => {
 
274
  }
275
  }
276
 
277
+ // --- TODAS AS OUTRAS FUNÇÕES ---
278
+
279
+ // ATIVIDADES
280
  async function generateActivity() {
281
  const activityType = activityTypeSelector.value;
282
  const context = contextSelector.value;
 
368
  submitActivityBtn.textContent = 'Submit Answer';
369
  }
370
  }
371
+
372
+ // CONVERSAÇÃO
373
+ function initializeSpeechRecognition() {
374
+ window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
375
+ if (!window.SpeechRecognition) {
376
+ micButton.disabled = true;
377
+ micButton.textContent = '❌';
378
+ alert('Voice recognition API is not supported in this browser.');
379
+ return;
380
+ }
381
+ recognition = new SpeechRecognition();
382
+ recognition.lang = 'en-US';
383
+ recognition.interimResults = false;
384
+ recognition.continuous = false;
385
+ recognition.onresult = (event) => {
386
+ const transcript = event.results[0][0].transcript;
387
+ chatInput.value = transcript;
388
+ sendMessage(transcript);
389
+ };
390
+ recognition.onerror = (event) => { console.error('Voice recognition error:', event.error); };
391
+ recognition.onend = () => {
392
+ isRecording = false;
393
+ micButton.classList.remove('is-recording');
394
+ micButton.innerHTML = '🎙️';
395
+ };
396
+ }
397
+ function handleVoiceInput() {
398
+ if (isRecording) {
399
+ recognition.stop();
400
+ } else {
401
+ isRecording = true;
402
+ micButton.classList.add('is-recording');
403
+ micButton.innerHTML = '<div class="spinner"></div>';
404
+ recognition.start();
405
+ }
406
+ }
407
+ function handleTextInput() {
408
+ const message = chatInput.value.trim();
409
+ sendMessage(message);
410
+ chatInput.value = '';
411
+ }
412
+ chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleTextInput(); });
413
+ async function sendMessage(message) {
414
+ if (!message) return;
415
+ appendMessage(message, 'user');
416
+ chatHistory.push({ role: 'user', content: message });
417
+ try {
418
+ const response = await fetch('/chat-with-ai', {
419
+ method: 'POST',
420
+ headers: { 'Content-Type': 'application/json' },
421
+ body: JSON.stringify({ history: chatHistory, message: message })
422
+ });
423
+ if (!response.ok) throw new Error((await response.json()).error);
424
+ const data = await response.json();
425
+ appendMessage(data.response, 'ai');
426
+ chatHistory.push({ role: 'assistant', content: data.response });
427
+ playAudio(data.response);
428
+ } catch (error) {
429
+ appendMessage(`Chat error: ${error.message}`, 'ai');
430
+ }
431
+ }
432
+ function appendMessage(text, sender) {
433
+ const msg = document.createElement('div');
434
+ msg.className = `chat-message ${sender}-message`;
435
+ msg.textContent = text;
436
+ chatDisplay.appendChild(msg);
437
+ chatDisplay.scrollTop = chatDisplay.scrollHeight;
438
+ }
439
+
440
+ // IMAGEM
441
+ imageUploader.addEventListener('change', (event) => {
442
+ const file = event.target.files[0];
443
+ if (!file) return;
444
+ const reader = new FileReader();
445
+ reader.onload = (e) => {
446
+ imagePreview.src = e.target.result;
447
+ imagePreview.classList.remove('hidden');
448
+ analyzeImage(e.target.result);
449
+ };
450
+ reader.readAsDataURL(file);
451
+ });
452
+ async function analyzeImage(base64Image) {
453
+ 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>`;
454
+ try {
455
+ const response = await fetch('/analyze-image', {
456
+ method: 'POST',
457
+ headers: { 'Content-Type': 'application/json' },
458
+ body: JSON.stringify({
459
+ image: base64Image,
460
+ model: modelSelector.value
461
+ })
462
+ });
463
+ if (!response.ok) throw new Error((await response.json()).error);
464
+ const vocabulary = await response.json();
465
+ displayImageVocabulary(vocabulary);
466
+ } catch (error) {
467
+ imageAnalysisResult.innerHTML = `<p class="text-red-600 font-semibold">Analysis failed: ${error.message}</p>`;
468
+ }
469
+ }
470
+ function displayImageVocabulary(vocabulary) {
471
+ imageAnalysisResult.innerHTML = '<h3 class="font-semibold mb-2">Suggested Vocabulary:</h3>';
472
+ const buttonContainer = document.createElement('div');
473
+ buttonContainer.className = 'flex flex-wrap gap-2';
474
+ vocabulary.forEach(({ term, definition }) => {
475
+ const btn = document.createElement('button');
476
+ btn.className = 'btn bg-violet-100 text-violet-700 hover:bg-violet-200 text-sm';
477
+ btn.textContent = term;
478
+ btn.onclick = () => createFlashcardFromSuggestion(term, definition);
479
+ buttonContainer.appendChild(btn);
480
+ });
481
+ imageAnalysisResult.appendChild(buttonContainer);
482
+ }
483
+ async function generateImage() {
484
+ const prompt = imagePromptInput.value.trim();
485
+ if (!prompt) {
486
+ alert('Please enter a description for the image.');
487
+ return;
488
+ }
489
+ generateImageBtn.disabled = true;
490
+ generateImageBtn.innerHTML = '<div class="spinner mr-2"></div> Generating...';
491
+ 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>`;
492
+ try {
493
+ const response = await fetch('/generate-image', {
494
+ method: 'POST',
495
+ headers: { 'Content-Type': 'application/json' },
496
+ body: JSON.stringify({ prompt: prompt })
497
+ });
498
+ if (!response.ok) throw new Error((await response.json()).error);
499
+ const data = await response.json();
500
+ imageResultContainer.innerHTML = `<img src="data:image/png;base64,${data.image_base64}" class="rounded-lg max-h-60 mx-auto" alt="AI-generated image"/>`;
501
+ } catch (error) {
502
+ imageResultContainer.innerHTML = `<p class="text-red-600 font-semibold">Failed to generate image: ${error.message}</p>`;
503
+ } finally {
504
+ generateImageBtn.disabled = false;
505
+ generateImageBtn.textContent = 'Generate';
506
+ }
507
+ }
508
+
509
+ // FLASHCARDS
510
+ async function createFlashcard(word, context, definition = null) {
511
+ const btn = definition ? null : createCardButton;
512
+ if (btn) {
513
+ btn.disabled = true;
514
+ btn.innerHTML = '<div class="spinner mr-2"></div> Creating...';
515
+ }
516
+ try {
517
+ const response = await fetch('/explain-proxy', {
518
+ method: 'POST',
519
+ headers: { 'Content-Type': 'application/json' },
520
+ body: JSON.stringify({
521
+ word: word,
522
+ context: context,
523
+ for_flashcard: true,
524
+ model: modelSelector.value,
525
+ context_focus: contextSelector.value
526
+ })
527
+ });
528
+ if (!response.ok) {
529
+ const errData = await response.json();
530
+ throw new Error(errData.error || 'Unknown server error.');
531
+ }
532
+ const cardData = await response.json();
533
+ if(definition && !cardData.definition) cardData.definition = definition;
534
+ renderFlashcard(cardData);
535
+ } catch (error) {
536
+ alert(`Failed to create flashcard: ${error.message}. Check API keys and model selection.`);
537
+ } finally {
538
+ if (btn) {
539
+ btn.disabled = false;
540
+ btn.innerHTML = '+ Create Flashcard';
541
+ }
542
+ }
543
+ }
544
+ function createFlashcardFromSelection() {
545
+ const selection = window.getSelection().toString().trim();
546
+ if (selection) createFlashcard(selection, textEditor.innerText);
547
+ }
548
+ function createFlashcardFromSuggestion(term, definition) {
549
+ createFlashcard(term, `(From image analysis) A visual representation of ${term}.`, definition);
550
+ }
551
+ function renderFlashcard(data) {
552
+ noCardsMessage.classList.add('hidden');
553
+ const cardId = `card-${Date.now()}`;
554
+ const container = document.createElement('div');
555
+ container.className = 'flashcard-container';
556
+ container.id = cardId;
557
+ const minHeight = 380;
558
+ container.style.minHeight = `${minHeight}px`;
559
+ const fallback = 'Info not generated';
560
+ container.innerHTML = `
561
+ <button class="delete-btn" onclick="deleteFlashcard('${cardId}', event)">🗑️</button>
562
+ <div class="flashcard-inner" style="min-height: ${minHeight}px;">
563
+ <div class="flashcard-front">
564
+ <div>
565
+ <div class="text-sm text-gray-600 mb-2">Context Sentence:</div>
566
+ <p class="text-lg text-center text-gray-800">${data.gapped_sentence || fallback}</p>
567
+ </div>
568
+ <div class="border-t pt-4 mt-4 text-center">
569
+ <strong class="text-indigo-600">Hint (PT):</strong> ${data.translation || fallback}
570
+ </div>
571
+ <div class="text-xs text-center text-gray-400 mt-4">Click to see the answer</div>
572
+ </div>
573
+ <div class="flashcard-back">
574
+ <div>
575
+ <h3 class="text-xl font-bold text-center text-indigo-600 mb-2">${data.term || 'Term'}</h3>
576
+ <p class="flex items-center justify-center gap-2 text-gray-600 italic mb-3">
577
+ <button class="audio-btn" onclick="playAudio('${(data.context_sentence || '').replace(/'/g, "\\'")}', event)">🔊</button>
578
+ <span>${data.context_sentence || fallback}</span>
579
+ </p>
580
+ <p class="text-sm text-gray-800"><strong class="font-semibold">Definition:</strong> ${data.definition || fallback}</p>
581
+ </div>
582
+ <div class="border-t mt-3 pt-3">
583
+ <h4 class="text-sm font-semibold text-center">Practice Your Pronunciation</h4>
584
+ <div class="flex justify-center items-center gap-2 mt-2">
585
+ <button class="btn btn-primary w-12 h-12 rounded-full" onclick="handlePronunciationPractice(this, '${(data.context_sentence || '').replace(/'/g, "\\'")}', event)">🎙️</button>
586
+ </div>
587
+ <div class="text-xs text-gray-600 p-2 mt-2 bg-gray-50 rounded-md min-h-[40px]" data-feedback-area></div>
588
+ </div>
589
+ </div>
590
+ </div>`;
591
+ flashcardList.prepend(container);
592
+ container.addEventListener('click', (e) => {
593
+ if(e.target.tagName !== 'BUTTON' && !e.target.closest('button')) {
594
+ container.classList.toggle('flipped');
595
+ }
596
+ });
597
+ }
598
+ function deleteFlashcard(cardId, event) {
599
+ event.stopPropagation();
600
+ const cardToDelete = document.getElementById(cardId);
601
+ if (cardToDelete) {
602
+ cardToDelete.remove();
603
+ }
604
+ if (flashcardList.children.length === 1) {
605
+ noCardsMessage.classList.remove('hidden');
606
+ }
607
+ }
608
+ async function handlePronunciationPractice(button, targetText, event) {
609
+ event.stopPropagation();
610
+ const feedbackArea = button.closest('.flashcard-back').querySelector('[data-feedback-area]');
611
+ const practiceRecognition = new SpeechRecognition();
612
+ practiceRecognition.lang = 'en-US';
613
+ button.innerHTML = '<div class="spinner"></div>';
614
+ button.disabled = true;
615
+ feedbackArea.textContent = 'Listening...';
616
+ practiceRecognition.start();
617
+ practiceRecognition.onresult = async (e) => {
618
+ const userText = e.results[0][0].transcript;
619
+ feedbackArea.textContent = `You said: "${userText}". Analyzing...`;
620
+ try {
621
+ const response = await fetch('/pronunciation-feedback', {
622
+ method: 'POST',
623
+ headers: { 'Content-Type': 'application/json' },
624
+ body: JSON.stringify({ target_text: targetText, user_text: userText })
625
+ });
626
+ if (!response.ok) throw new Error((await response.json()).error);
627
+ const data = await response.json();
628
+ feedbackArea.innerHTML = `<strong>Feedback:</strong> ${data.feedback}`;
629
+ } catch (error) {
630
+ feedbackArea.textContent = `Analysis error: ${error.message}`;
631
+ } finally {
632
+ button.innerHTML = '🎙️';
633
+ button.disabled = false;
634
+ }
635
+ };
636
+ practiceRecognition.onerror = (e) => {
637
+ feedbackArea.textContent = `Recording error: ${e.error}`;
638
+ button.innerHTML = '🎙️';
639
+ button.disabled = false;
640
+ };
641
+ }
642
 
643
  // --- INICIALIZAÇÃO ---
644
  document.addEventListener('DOMContentLoaded', () => {