Spaces:
Sleeping
Sleeping
Update index.html
Browse files- index.html +114 -11
index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
| 15 |
.btn-primary:hover:not(:disabled) { background-color: #4338ca; }
|
| 16 |
.spinner { border: 2px solid rgba(255,255,255,0.3); width: 20px; height: 20px; border-radius: 50%; border-left-color: #fff; animation: spin 1s ease infinite; }
|
| 17 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 18 |
-
.flashcard-container { perspective: 1000px; position: relative; }
|
| 19 |
.flashcard-inner { position: relative; width: 100%; transition: transform 0.6s; transform-style: preserve-3d; }
|
| 20 |
.flashcard-container.flipped .flashcard-inner { transform: rotateY(180deg); }
|
| 21 |
.flashcard-front, .flashcard-back { position: absolute; width: 100%; height:100%; backface-visibility: hidden; border: 1px solid #e5e7eb; border-radius: 12px; padding: 20px; display: flex; flex-direction: column; justify-content: space-between; }
|
|
@@ -84,6 +84,30 @@
|
|
| 84 |
<p class="text-gray-500 italic">Sua imagem aparecerá aqui.</p>
|
| 85 |
</div>
|
| 86 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
<div class="card">
|
| 89 |
<h2 class="text-xl font-semibold mb-4 text-gray-700">Configurações de IA</h2>
|
|
@@ -92,13 +116,13 @@
|
|
| 92 |
<label for="modelSelector" class="block text-sm font-medium text-gray-700">Modelo de IA (Flashcards)</label>
|
| 93 |
<select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
|
| 94 |
<optgroup label="Google Gemini">
|
| 95 |
-
<option value="gemini:gemini-1.5-flash-latest">Gemini
|
| 96 |
-
<option value="gemini:gemini-1.5-pro-latest">Gemini
|
| 97 |
</optgroup>
|
| 98 |
<optgroup label="Groq (Ultra Rápido)">
|
| 99 |
<option value="groq:llama-3.1-8b-instant">Llama 3.1 8B</option>
|
| 100 |
-
<option value="groq:
|
| 101 |
-
<option value="groq:
|
| 102 |
</optgroup>
|
| 103 |
</select>
|
| 104 |
</div>
|
|
@@ -146,6 +170,10 @@
|
|
| 146 |
const imagePromptInput = document.getElementById('imagePromptInput');
|
| 147 |
const generateImageBtn = document.getElementById('generateImageBtn');
|
| 148 |
const imageResultContainer = document.getElementById('imageResultContainer');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
let chatHistory = [];
|
| 151 |
let recognition;
|
|
@@ -156,6 +184,7 @@
|
|
| 156 |
const len = textEditor.innerText.length;
|
| 157 |
charCount.textContent = `${len}/10000`;
|
| 158 |
playButton.disabled = len === 0;
|
|
|
|
| 159 |
});
|
| 160 |
textEditor.addEventListener('mouseup', () => {
|
| 161 |
createCardButton.disabled = !window.getSelection().toString().trim();
|
|
@@ -320,6 +349,84 @@
|
|
| 320 |
generateImageBtn.textContent = 'Gerar';
|
| 321 |
}
|
| 322 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
// --- LÓGICA DE GERAÇÃO DE FLASHCARD ---
|
| 325 |
async function createFlashcard(word, context, definition = null) {
|
|
@@ -408,27 +515,23 @@
|
|
| 408 |
|
| 409 |
flashcardList.prepend(container);
|
| 410 |
container.addEventListener('click', (e) => {
|
| 411 |
-
// Evita virar o card se um botão dentro dele for clicado
|
| 412 |
if(e.target.tagName !== 'BUTTON' && !e.target.closest('button')) {
|
| 413 |
container.classList.toggle('flipped');
|
| 414 |
}
|
| 415 |
});
|
| 416 |
}
|
| 417 |
|
| 418 |
-
// --- NOVA FUNÇÃO PARA EXCLUIR FLASHCARD ---
|
| 419 |
function deleteFlashcard(cardId, event) {
|
| 420 |
-
event.stopPropagation();
|
| 421 |
const cardToDelete = document.getElementById(cardId);
|
| 422 |
if (cardToDelete) {
|
| 423 |
cardToDelete.remove();
|
| 424 |
}
|
| 425 |
-
|
| 426 |
-
if (flashcardList.children.length === 1) { // Apenas a mensagem <p> restará
|
| 427 |
noCardsMessage.classList.remove('hidden');
|
| 428 |
}
|
| 429 |
}
|
| 430 |
|
| 431 |
-
// --- LÓGICA DE PRÁTICA DE PRONÚNCIA ---
|
| 432 |
async function handlePronunciationPractice(button, targetText, event) {
|
| 433 |
event.stopPropagation();
|
| 434 |
const feedbackArea = button.closest('.flashcard-back').querySelector('[data-feedback-area]');
|
|
|
|
| 15 |
.btn-primary:hover:not(:disabled) { background-color: #4338ca; }
|
| 16 |
.spinner { border: 2px solid rgba(255,255,255,0.3); width: 20px; height: 20px; border-radius: 50%; border-left-color: #fff; animation: spin 1s ease infinite; }
|
| 17 |
@keyframes spin { to { transform: rotate(360deg); } }
|
| 18 |
+
.flashcard-container { perspective: 1000px; position: relative; }
|
| 19 |
.flashcard-inner { position: relative; width: 100%; transition: transform 0.6s; transform-style: preserve-3d; }
|
| 20 |
.flashcard-container.flipped .flashcard-inner { transform: rotateY(180deg); }
|
| 21 |
.flashcard-front, .flashcard-back { position: absolute; width: 100%; height:100%; backface-visibility: hidden; border: 1px solid #e5e7eb; border-radius: 12px; padding: 20px; display: flex; flex-direction: column; justify-content: space-between; }
|
|
|
|
| 84 |
<p class="text-gray-500 italic">Sua imagem aparecerá aqui.</p>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
+
|
| 88 |
+
<div class="card">
|
| 89 |
+
<h2 class="text-xl font-semibold mb-4 text-gray-700">5. Atividades de Escrita e Tradução</h2>
|
| 90 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
| 91 |
+
<div>
|
| 92 |
+
<label for="activityTypeSelector" class="block text-sm font-medium text-gray-700">Tipo de Atividade</label>
|
| 93 |
+
<select id="activityTypeSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
|
| 94 |
+
<option value="translation_pt_en">Tradução (PT para EN)</option>
|
| 95 |
+
<option value="email_writing">Escrever E-mail Formal</option>
|
| 96 |
+
<option value="summary_writing">Resumir o Texto</option>
|
| 97 |
+
</select>
|
| 98 |
+
</div>
|
| 99 |
+
<div class="flex items-end">
|
| 100 |
+
<button id="generateActivityButton" onclick="generateActivity()" class="btn btn-primary w-full">Gerar Atividade</button>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
<div class="mt-4 border-t pt-4">
|
| 104 |
+
<button id="translateFullTextButton" onclick="translateFullText()" class="btn btn-primary bg-gray-500 hover:bg-gray-600 w-full" disabled>Traduzir Texto Completo para PT</button>
|
| 105 |
+
</div>
|
| 106 |
+
<div id="activityOutput" class="mt-4 p-4 border rounded-lg bg-gray-50 min-h-[100px] whitespace-pre-wrap">
|
| 107 |
+
<p class="text-gray-500 italic">Seu exercício aparecerá aqui.</p>
|
| 108 |
+
</div>
|
| 109 |
+
</div>
|
| 110 |
+
|
| 111 |
|
| 112 |
<div class="card">
|
| 113 |
<h2 class="text-xl font-semibold mb-4 text-gray-700">Configurações de IA</h2>
|
|
|
|
| 116 |
<label for="modelSelector" class="block text-sm font-medium text-gray-700">Modelo de IA (Flashcards)</label>
|
| 117 |
<select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
|
| 118 |
<optgroup label="Google Gemini">
|
| 119 |
+
<option value="gemini:gemini-1.5-flash-latest">Gemini 2.5 Flash</option>
|
| 120 |
+
<option value="gemini:gemini-1.5-pro-latest">Gemini 2.5 Pro</option>
|
| 121 |
</optgroup>
|
| 122 |
<optgroup label="Groq (Ultra Rápido)">
|
| 123 |
<option value="groq:llama-3.1-8b-instant">Llama 3.1 8B</option>
|
| 124 |
+
<option value="groq:llama-3.1-70b-versatile">Llama 3.1 70B</option>
|
| 125 |
+
<option value="groq:openai/gpt-oss-120b">OpenAI GPT-OSS 120B</option>
|
| 126 |
</optgroup>
|
| 127 |
</select>
|
| 128 |
</div>
|
|
|
|
| 170 |
const imagePromptInput = document.getElementById('imagePromptInput');
|
| 171 |
const generateImageBtn = document.getElementById('generateImageBtn');
|
| 172 |
const imageResultContainer = document.getElementById('imageResultContainer');
|
| 173 |
+
const activityTypeSelector = document.getElementById('activityTypeSelector');
|
| 174 |
+
const generateActivityButton = document.getElementById('generateActivityButton');
|
| 175 |
+
const activityOutput = document.getElementById('activityOutput');
|
| 176 |
+
const translateFullTextButton = document.getElementById('translateFullTextButton');
|
| 177 |
|
| 178 |
let chatHistory = [];
|
| 179 |
let recognition;
|
|
|
|
| 184 |
const len = textEditor.innerText.length;
|
| 185 |
charCount.textContent = `${len}/10000`;
|
| 186 |
playButton.disabled = len === 0;
|
| 187 |
+
translateFullTextButton.disabled = len === 0;
|
| 188 |
});
|
| 189 |
textEditor.addEventListener('mouseup', () => {
|
| 190 |
createCardButton.disabled = !window.getSelection().toString().trim();
|
|
|
|
| 349 |
generateImageBtn.textContent = 'Gerar';
|
| 350 |
}
|
| 351 |
}
|
| 352 |
+
|
| 353 |
+
// --- LÓGICA DE ATIVIDADES DE ESCRITA E TRADUÇÃO ---
|
| 354 |
+
async function generateActivity() {
|
| 355 |
+
const activityType = activityTypeSelector.value;
|
| 356 |
+
const context = contextSelector.value;
|
| 357 |
+
const sourceText = textEditor.innerText;
|
| 358 |
+
|
| 359 |
+
generateActivityButton.disabled = true;
|
| 360 |
+
generateActivityButton.innerHTML = '<div class="spinner mr-2"></div> Gerando...';
|
| 361 |
+
activityOutput.innerHTML = '<p class="text-gray-500 italic">Gerando seu exercício...</p>';
|
| 362 |
+
|
| 363 |
+
let prompt = "";
|
| 364 |
+
if (activityType === 'translation_pt_en') {
|
| 365 |
+
prompt = `Baseado no contexto '${context}', crie uma frase em português para eu traduzir para o inglês.`;
|
| 366 |
+
} else if (activityType === 'email_writing') {
|
| 367 |
+
prompt = `Crie um cenário para eu escrever um e-mail formal, relacionado ao contexto '${context}'.`;
|
| 368 |
+
} else if (activityType === 'summary_writing') {
|
| 369 |
+
if (!sourceText) {
|
| 370 |
+
alert("Por favor, cole um texto no editor para gerar a atividade de resumo.");
|
| 371 |
+
activityOutput.innerHTML = '<p class="text-red-500">Cole um texto acima primeiro.</p>';
|
| 372 |
+
generateActivityButton.disabled = false;
|
| 373 |
+
generateActivityButton.textContent = 'Gerar Atividade';
|
| 374 |
+
return;
|
| 375 |
+
}
|
| 376 |
+
prompt = `Resuma o seguinte texto em uma frase: "${sourceText}"`;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
try {
|
| 380 |
+
const response = await fetch('/explain-proxy', {
|
| 381 |
+
method: 'POST',
|
| 382 |
+
headers: { 'Content-Type': 'application/json' },
|
| 383 |
+
body: JSON.stringify({
|
| 384 |
+
model: modelSelector.value,
|
| 385 |
+
context_focus: context,
|
| 386 |
+
custom_prompt: prompt
|
| 387 |
+
})
|
| 388 |
+
});
|
| 389 |
+
if (!response.ok) throw new Error((await response.json()).error);
|
| 390 |
+
const data = await response.json();
|
| 391 |
+
activityOutput.textContent = data.explanation;
|
| 392 |
+
} catch (error) {
|
| 393 |
+
activityOutput.innerHTML = `<p class="text-red-600 font-semibold">Falha ao gerar atividade: ${error.message}</p>`;
|
| 394 |
+
} finally {
|
| 395 |
+
generateActivityButton.disabled = false;
|
| 396 |
+
generateActivityButton.textContent = 'Gerar Atividade';
|
| 397 |
+
}
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
async function translateFullText() {
|
| 401 |
+
const text = textEditor.innerText;
|
| 402 |
+
if (!text) return;
|
| 403 |
+
|
| 404 |
+
translateFullTextButton.disabled = true;
|
| 405 |
+
translateFullTextButton.innerHTML = '<div class="spinner mr-2"></div> Traduzindo...';
|
| 406 |
+
|
| 407 |
+
const prompt = `Traduza o seguinte texto para o português de forma clara e natural: "${text}"`;
|
| 408 |
+
|
| 409 |
+
try {
|
| 410 |
+
const response = await fetch('/explain-proxy', {
|
| 411 |
+
method: 'POST',
|
| 412 |
+
headers: { 'Content-Type': 'application/json' },
|
| 413 |
+
body: JSON.stringify({
|
| 414 |
+
model: modelSelector.value,
|
| 415 |
+
context_focus: contextSelector.value,
|
| 416 |
+
custom_prompt: prompt
|
| 417 |
+
})
|
| 418 |
+
});
|
| 419 |
+
if (!response.ok) throw new Error((await response.json()).error);
|
| 420 |
+
const data = await response.json();
|
| 421 |
+
alert("Tradução Completa:\n\n" + data.explanation);
|
| 422 |
+
} catch (error) {
|
| 423 |
+
alert(`Falha na tradução: ${error.message}`);
|
| 424 |
+
} finally {
|
| 425 |
+
translateFullTextButton.disabled = false;
|
| 426 |
+
translateFullTextButton.textContent = 'Traduzir Texto Completo para PT';
|
| 427 |
+
}
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
|
| 431 |
// --- LÓGICA DE GERAÇÃO DE FLASHCARD ---
|
| 432 |
async function createFlashcard(word, context, definition = null) {
|
|
|
|
| 515 |
|
| 516 |
flashcardList.prepend(container);
|
| 517 |
container.addEventListener('click', (e) => {
|
|
|
|
| 518 |
if(e.target.tagName !== 'BUTTON' && !e.target.closest('button')) {
|
| 519 |
container.classList.toggle('flipped');
|
| 520 |
}
|
| 521 |
});
|
| 522 |
}
|
| 523 |
|
|
|
|
| 524 |
function deleteFlashcard(cardId, event) {
|
| 525 |
+
event.stopPropagation();
|
| 526 |
const cardToDelete = document.getElementById(cardId);
|
| 527 |
if (cardToDelete) {
|
| 528 |
cardToDelete.remove();
|
| 529 |
}
|
| 530 |
+
if (flashcardList.children.length === 1) {
|
|
|
|
| 531 |
noCardsMessage.classList.remove('hidden');
|
| 532 |
}
|
| 533 |
}
|
| 534 |
|
|
|
|
| 535 |
async function handlePronunciationPractice(button, targetText, event) {
|
| 536 |
event.stopPropagation();
|
| 537 |
const feedbackArea = button.closest('.flashcard-back').querySelector('[data-feedback-area]');
|