amauricunha commited on
Commit
397925c
·
verified ·
1 Parent(s): 81e20b3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +292 -720
index.html CHANGED
@@ -1,764 +1,336 @@
1
- <html lang="en">
 
2
  <head>
3
  <meta charset="UTF-8">
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
  <title>Dynamic English Study Studio</title>
6
  <script src="https://cdn.tailwindcss.com"></script>
7
  <style>
8
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
9
- body {
10
- font-family: 'Inter', sans-serif;
11
- background-color: #f7f9fc;
12
- }
13
- .container {
14
- max-width: 1000px;
15
- margin: 0 auto;
16
- padding: 20px;
17
- }
18
- .card {
19
- background-color: white;
20
- border-radius: 12px;
21
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
22
- padding: 24px;
23
- }
24
- .btn-primary {
25
- background-color: #4f46e5;
26
- color: white;
27
- transition: background-color 0.2s;
28
- }
29
- .btn-primary:hover {
30
- background-color: #4338ca;
31
- }
32
- .btn-secondary {
33
- background-color: #e5e7eb;
34
- color: #1f2937;
35
- transition: background-color 0.2s;
36
- }
37
- .btn-secondary:hover {
38
- background-color: #d1d5db;
39
- }
40
- .btn-success {
41
- background-color: #10b981;
42
- color: white;
43
- transition: background-color 0.2s;
44
- }
45
- .btn-success:hover {
46
- background-color: #059669;
47
- }
48
- /* Style for the Content Editable Editor */
49
- #textEditor {
50
- border: 1px solid #ccc;
51
- padding: 1rem;
52
- min-height: 16rem; /* h-64 equivalent */
53
- border-radius: 0.5rem;
54
- color: #1f2937;
55
- line-height: 1.6;
56
- outline: none;
57
- overflow-y: auto;
58
- /* Force English spellcheck */
59
- -webkit-user-modify: read-write-plaintext-only;
60
- spellcheck: true;
61
- }
62
- /* Styling for the Modal (Carousel Simulation) */
63
- .modal {
64
- display: none;
65
- position: fixed;
66
- z-index: 1000;
67
- left: 0;
68
- top: 0;
69
- width: 100%;
70
- height: 100%;
71
- overflow: auto;
72
- background-color: rgba(0,0,0,0.4);
73
- backdrop-filter: blur(5px);
74
- }
75
- .modal-content {
76
- background-color: #fff;
77
- margin: 10% auto;
78
- padding: 20px;
79
- border-radius: 10px;
80
- width: 90%;
81
- max-width: 500px;
82
- box-shadow: 0 8px 25px rgba(0,0,0,0.2);
83
- animation: slideIn 0.3s ease-out;
84
- }
85
- @keyframes slideIn {
86
- from { opacity: 0; transform: translateY(-30px); }
87
- to { opacity: 1; transform: translateY(0); }
88
- }
89
- .close {
90
- color: #aaa;
91
- float: right;
92
- font-size: 28px;
93
- font-weight: bold;
94
- }
95
- .close:hover,
96
- .close:focus {
97
- color: #333;
98
- text-decoration: none;
99
- cursor: pointer;
100
- }
101
- .tab-button.active {
102
- background-color: #4f46e5;
103
- color: white;
104
- border-bottom: 2px solid #4f46e5;
105
- }
106
- /* Flashcard style */
107
- .flashcard {
108
- background-color: #f3f4f6;
109
- padding: 15px;
110
- border-radius: 8px;
111
- margin-bottom: 10px;
112
- cursor: pointer;
113
- transition: all 0.2s;
114
- border: 1px solid #e5e7eb;
115
- }
116
- .flashcard:hover {
117
- background-color: #e5e7eb;
118
- box-shadow: 0 2px 5px rgba(0,0,0,0.05);
119
- }
120
- .flashcard-term {
121
- font-weight: 600;
122
- color: #4f46e5;
123
- text-decoration: underline;
124
- }
125
- .flashcard-definition {
126
- font-size: 0.9em;
127
- color: #4b5563;
128
- }
129
- .flashcard-example {
130
- font-style: italic;
131
- font-size: 0.8em;
132
- color: #6b7280;
133
- margin-top: 5px;
134
- }
135
- /* Loading Style (Spinner) */
136
- .spinner {
137
- border: 4px solid rgba(0, 0, 0, 0.1);
138
- width: 24px;
139
- height: 24px;
140
- border-radius: 50%;
141
- border-left-color: #4f46e5;
142
- animation: spin 1s ease infinite;
143
- }
144
- @keyframes spin {
145
- 0% { transform: rotate(0deg); }
146
- 100% { transform: rotate(360deg); }
147
- }
148
  </style>
149
  </head>
150
- <body>
151
 
152
- <div class="container py-8">
153
  <header class="text-center mb-10">
154
- <h1 class="text-3xl font-bold text-gray-800">Dynamic English Study Studio</h1>
155
- <p class="text-gray-500">Audio and Contextualization tool for language studies.</p>
156
  </header>
157
 
158
- <div class="card mb-6">
159
- <h2 class="text-xl font-semibold mb-4 text-gray-700">Your Text for Study</h2>
160
-
161
- <!-- CONTENT EDITABLE DIV replaces TEXTAREA -->
162
- <div id="textEditor"
163
- contenteditable="true"
164
- spellcheck="true"
165
- lang="en"
166
- data-placeholder="Paste the English text you want to listen to and study here. Double-click any word for translation and context!"
167
- class="w-full p-4 focus:ring-indigo-500 focus:border-indigo-500 text-gray-800"></div>
168
-
169
- <p class="text-sm text-right text-gray-500 mt-2">Characters: <span id="charCount">0</span>/5000</p>
170
-
171
- <div class="mt-4 border-t pt-4">
172
- <button id="createCardButton" onclick="createFlashcardFromSelection()" class="btn-success px-4 py-2 rounded-lg font-medium disabled:opacity-50" disabled>
173
- + Create Flashcard from Selection
174
- </button>
175
- </div>
176
- </div>
177
-
178
- <div class="card mb-6">
179
- <h2 class="text-xl font-semibold mb-4 text-gray-700">LLM and Audio Controls</h2>
180
- <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
181
-
182
- <!-- LLM Provider Selector -->
183
- <div class="md:col-span-2">
184
- <label for="modelSelector" class="block text-sm font-medium text-gray-700">AI Model Provider (Context/Translation)</label>
185
- <select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md shadow-sm">
186
- <!-- Format: provider:model-name -->
187
- <option value="gemini:gemini-2.5-flash">Google Gemini (Free Tier)</option>
188
- <option value="groq:mixtral-8x7b-32768">Groq (Mixtral 8x7b - Fast)</option>
189
- <option value="groq:llama3-8b-8192">Groq (Llama 3 8B)</option>
190
- </select>
191
  </div>
192
 
193
- <!-- Speed Control -->
194
- <div>
195
- <label for="speedSelector" class="block text-sm font-medium text-gray-700">Speaking Rate (gTTS)</label>
196
- <select id="speedSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md shadow-sm">
197
- <option value="1.0">Normal (1.0x)</option>
198
- <option value="0.75">Slow (0.75x)</option>
199
- <option value="1.25">Fast (1.25x)</option>
200
- </select>
201
- </div>
202
-
203
- <!-- Main Play Button -->
204
- <div class="md:col-span-1 flex items-end">
205
- <button id="playButton" onclick="handlePlay()" class="btn-primary w-full py-2.5 rounded-lg font-semibold flex items-center justify-center disabled:opacity-50" disabled>
206
- <span id="playText">Generate & Play Audio</span>
207
- <div id="loadingSpinner" class="spinner ml-3 hidden"></div>
208
- </button>
209
- </div>
210
- </div>
211
-
212
- <!-- Context Selector (NEW) -->
213
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
214
- <div>
215
- <label for="contextSelector" class="block text-sm font-medium text-gray-700">Vocabulary Context / Focus Area</label>
216
- <select id="contextSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md shadow-sm">
217
- <option value="General/Social (Casual conversation, daily life)">General/Social (Casual conversation, daily life)</option>
218
- <option value="Professional/Business (Meetings, emails, reports)">Professional/Business (Meetings, emails, reports)</option>
219
- <option value="Technical/IT (Software, Engineering, Tech topics)">Technical/IT (Software, Engineering, Tech topics)</option>
220
- </select>
221
  </div>
222
- <!-- Full Translation Button -->
223
- <div class="flex items-end">
224
- <button id="translateFullTextButton" onclick="translateFullText()" class="btn-secondary w-full py-2.5 rounded-lg font-semibold disabled:opacity-50">
225
- Translate Full Text (PT)
226
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  </div>
228
  </div>
229
 
230
- <!-- Player Controls and Phrase Loop -->
231
- <div class="mt-4 border-t pt-4">
232
- <audio id="audioPlayer" controls class="w-full mb-4"></audio>
233
-
234
- <div class="flex flex-wrap gap-3">
235
- <button id="loopSelectionButton" onclick="setLoopMode()" class="btn-secondary px-4 py-2 rounded-lg font-medium disabled:opacity-50" disabled>
236
- Loop Selection (Repeat Phrase)
237
- </button>
238
- <button id="clearLoopButton" onclick="clearLoopMode()" class="btn-secondary px-4 py-2 rounded-lg font-medium disabled:opacity-50" disabled>
239
- Clear Loop
240
- </button>
241
  </div>
242
  </div>
243
  </div>
244
-
245
- <!-- Writing/Translation Activity Panel (NEW) -->
246
- <div class="card mb-6">
247
- <h2 class="text-xl font-semibold mb-4 text-gray-700">Writing & Translation Activities</h2>
248
- <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
249
- <!-- Activity Selector -->
250
- <div class="md:col-span-2">
251
- <label for="activityTypeSelector" class="block text-sm font-medium text-gray-700">Activity Type</label>
252
- <select id="activityTypeSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md shadow-sm">
253
- <option value="translation_pt_en">Translation from PT to EN (Practice Writing)</option>
254
- <option value="email_writing">Formal Email Prompt (Professional Practice)</option>
255
- <option value="summary_writing">Summarization Prompt (Reading & Writing)</option>
256
- </select>
257
- </div>
258
- <!-- Generate Button -->
259
- <div class="md:col-span-1 flex items-end">
260
- <button id="generateActivityButton" onclick="generateActivity()" class="btn-primary w-full py-2.5 rounded-lg font-semibold flex items-center justify-center">
261
- <span id="activityText">Generate Activity</span>
262
- <div id="activitySpinner" class="spinner ml-3 hidden"></div>
263
- </button>
264
- </div>
265
- </div>
266
-
267
- <!-- Activity Output -->
268
- <div id="activityOutput" class="border p-4 mt-4 rounded-lg bg-gray-50 min-h-[100px]">
269
- <p class="text-gray-500 italic">Select an activity and context to generate your first prompt.</p>
270
- </div>
271
- </div>
272
-
273
-
274
- <!-- Flashcards Panel -->
275
- <div id="flashcardsPanel" class="card">
276
- <h2 class="text-xl font-semibold mb-4 text-gray-700">Your Flashcards (Study Session)</h2>
277
- <div id="flashcardList" class="space-y-3">
278
- <p id="noCardsMessage" class="text-gray-500 italic">No flashcards created yet. Select a word and click 'Create Flashcard'.</p>
279
- </div>
280
- </div>
281
-
282
  </div>
283
 
284
- <!-- Translation and Context Modal -->
285
  <div id="explanationModal" class="modal">
286
- <div class="modal-content">
287
- <span class="close" onclick="closeModal()">&times;</span>
288
- <h3 class="text-2xl font-bold mb-4 text-gray-800">Word Analysis: <span id="wordSelected" class="text-indigo-600"></span></h3>
289
 
290
- <!-- Tab Navigation (Carousel Simulation) -->
291
- <div class="flex border-b mb-4">
292
- <button class="tab-button active px-4 py-2 text-sm font-medium rounded-t-lg" data-tab="explanation">Explanation (EN)</button>
293
- <button class="tab-button px-4 py-2 text-sm font-medium rounded-t-lg" data-tab="translation">Translation (PT)</button>
294
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
- <!-- Tab Content -->
297
- <div id="modalContent">
298
- <div id="explanationTab" class="tab-content">
299
- <p class="text-gray-600 italic">Context and Definition in English:</p>
300
- <div id="explanationResult" class="mt-2 p-3 bg-gray-50 rounded-md min-h-[50px]">
301
- <div id="explanationSpinner" class="spinner mx-auto hidden"></div>
302
- <p id="explanationText" class="text-gray-800"></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  </div>
 
304
  </div>
305
- <div id="translationTab" class="tab-content hidden">
306
- <p class="text-gray-600 italic">Translation to Portuguese:</p>
307
- <div id="translationResult" class="mt-2 p-3 bg-gray-50 rounded-md min-h-[50px]">
308
- <div id="translationSpinner" class="spinner mx-auto hidden"></div>
309
- <p id="translationText" class="text-gray-800"></p>
 
 
 
 
 
 
 
 
 
310
  </div>
311
  </div>
312
  </div>
313
-
314
- <button onclick="closeModal()" class="btn-primary mt-6 w-full py-2.5 rounded-lg font-semibold">Close</button>
315
- </div>
316
- </div>
317
-
318
- <script>
319
- // Global variables
320
- const textEditor = document.getElementById('textEditor');
321
- const playButton = document.getElementById('playButton');
322
- const audioPlayer = document.getElementById('audioPlayer');
323
- const charCount = document.getElementById('charCount');
324
- const loadingSpinner = document.getElementById('loadingSpinner');
325
- const playText = document.getElementById('playText');
326
- const loopSelectionButton = document.getElementById('loopSelectionButton');
327
- const clearLoopButton = document.getElementById('clearLoopButton');
328
- const createCardButton = document.getElementById('createCardButton');
329
- const flashcardList = document.getElementById('flashcardList');
330
- const noCardsMessage = document.getElementById('noCardsMessage');
331
- const speedSelector = document.getElementById('speedSelector');
332
- const modelSelector = document.getElementById('modelSelector');
333
- const contextSelector = document.getElementById('contextSelector'); // NEW
334
- const activityTypeSelector = document.getElementById('activityTypeSelector'); // NEW
335
- const generateActivityButton = document.getElementById('generateActivityButton'); // NEW
336
- const activityOutput = document.getElementById('activityOutput'); // NEW
337
- const activitySpinner = document.getElementById('activitySpinner'); // NEW
338
- const activityText = document.getElementById('activityText'); // NEW
339
- const translateFullTextButton = document.getElementById('translateFullTextButton'); // NEW
340
-
341
- const MAX_CHARS = 5000;
342
- let isLooping = false;
343
- let loopText = '';
344
- let lastAudioUrl = '';
345
-
346
- // --- Interface and Editor Management ---
347
-
348
- function getEditorText() {
349
- return textEditor.innerText.trim();
350
- }
351
-
352
- // Add placeholder functionality for contenteditable
353
- function updatePlaceholder() {
354
- const placeholder = textEditor.getAttribute('data-placeholder');
355
- if (getEditorText() === '' && textEditor.innerHTML === '') {
356
- textEditor.innerHTML = `<span style="color:#9ca3af">${placeholder}</span>`;
357
- } else if (textEditor.innerText.includes(placeholder)) {
358
- textEditor.innerHTML = '';
359
- }
360
- }
361
-
362
- function removePlaceholder() {
363
- const placeholderText = textEditor.getAttribute('data-placeholder');
364
- if (textEditor.innerHTML.includes(placeholderText)) {
365
- textEditor.innerHTML = '';
366
- }
367
- }
368
 
369
- // Initial setup for placeholder
370
- updatePlaceholder();
371
- textEditor.addEventListener('focus', removePlaceholder);
372
- textEditor.addEventListener('blur', updatePlaceholder);
373
-
374
- textEditor.addEventListener('input', () => {
375
- const currentLength = getEditorText().length;
376
- charCount.textContent = currentLength;
377
- playButton.disabled = currentLength === 0;
378
- // Enable/disable translate full text button based on content
379
- translateFullTextButton.disabled = currentLength === 0;
380
  });
381
-
382
- // Update character count on load
383
- textEditor.dispatchEvent(new Event('input'));
384
-
385
- speedSelector.addEventListener('change', () => {
386
- audioPlayer.playbackRate = parseFloat(speedSelector.value);
387
- });
388
-
389
- // Enable Loop and Card button if there is a selection
390
- textEditor.addEventListener('mouseup', () => {
391
- const selection = window.getSelection().toString().trim();
392
- loopSelectionButton.disabled = !selection;
393
- createCardButton.disabled = !selection;
394
- });
395
-
396
- // --- Main Audio and Loop Functionality ---
397
-
398
- async function fetchAudio(text) {
399
- playButton.disabled = true;
400
- playText.textContent = "Generating Audio...";
401
- loadingSpinner.classList.remove('hidden');
402
-
403
- try {
404
- const response = await fetch('/tts-proxy', {
405
- method: 'POST',
406
- headers: { 'Content-Type': 'application/json' },
407
- body: JSON.stringify({ text: text })
408
- });
409
-
410
- if (!response.ok) {
411
- throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
412
- }
413
-
414
- const audioBlob = await response.blob();
415
-
416
- if (lastAudioUrl) URL.revokeObjectURL(lastAudioUrl);
417
-
418
- lastAudioUrl = URL.createObjectURL(audioBlob);
419
- audioPlayer.src = lastAudioUrl;
420
- audioPlayer.playbackRate = parseFloat(speedSelector.value);
421
- audioPlayer.play();
422
-
423
- } catch (error) {
424
- console.error('Error generating audio:', error);
425
- alert('Error generating audio. Check the console or the status of your backend (app.py).');
426
- } finally {
427
- playButton.disabled = false;
428
- playText.textContent = "Generate & Play Audio";
429
- loadingSpinner.classList.add('hidden');
430
- }
431
- }
432
-
433
- function handlePlay() {
434
- isLooping = false;
435
- clearLoopButton.disabled = true;
436
- const textToPlay = getEditorText();
437
- if (textToPlay) {
438
- fetchAudio(textToPlay);
439
- }
440
- }
441
-
442
- function setLoopMode() {
443
- const selection = window.getSelection().toString().trim();
444
- if (selection) {
445
- loopText = selection;
446
- isLooping = true;
447
- clearLoopButton.disabled = false;
448
- fetchAudio(loopText);
449
- audioPlayer.title = 'Looping: ' + loopText;
450
- } else {
451
- alert('Select the text you wish to repeat.');
452
- }
453
- }
454
-
455
- function clearLoopMode() {
456
- isLooping = false;
457
- loopText = '';
458
- clearLoopButton.disabled = true;
459
- audioPlayer.loop = false;
460
- audioPlayer.title = '';
461
- if (getEditorText()) {
462
- handlePlay();
463
- }
464
- }
465
-
466
- audioPlayer.addEventListener('ended', () => {
467
- if (isLooping) {
468
- audioPlayer.currentTime = 0;
469
- audioPlayer.play();
470
- }
471
- });
472
-
473
- // --- Full Text Translation Functionality (NEW) ---
474
-
475
- async function translateFullText() {
476
- const fullText = getEditorText();
477
- if (!fullText) {
478
- alert("Please enter text to translate.");
479
- return;
480
- }
481
-
482
- translateFullTextButton.disabled = true;
483
- translateFullTextButton.innerHTML = `<div class="spinner mr-2"></div> Translating...`;
484
- const modelValue = modelSelector.value;
485
- const context = contextSelector.value;
486
-
487
- try {
488
- // Use a generic prompt for full translation
489
- const prompt = `Translate the following English text into clear, modern Portuguese, ensuring the translation adheres to the context: ${context}.\n\nENGLISH TEXT:\n---${fullText}---`;
490
-
491
- const response = await fetch('/explain-proxy', { // Reusing explain-proxy for simple text generation
492
- method: 'POST',
493
- headers: { 'Content-Type': 'application/json' },
494
- body: JSON.stringify({
495
- word: "FULL_TRANSLATION", // Marker word, not used by backend for full text
496
- context: fullText, // Full text as context
497
- for_flashcard: false,
498
- model: modelValue,
499
- custom_prompt: prompt // Pass the custom prompt
500
- })
501
- });
502
-
503
- if (!response.ok) {
504
- throw new Error(`HTTP Error: ${response.status}. Check API Keys.`);
505
- }
506
-
507
- const data = await response.json();
508
- const translation = data.translation;
509
-
510
- // Open a temporary modal or alert the user with the result
511
- alert("FULL TEXT TRANSLATION:\n\n" + translation);
512
-
513
- } catch (error) {
514
- console.error('Error translating full text:', error);
515
- alert('Failed to translate full text. Check console and API Keys.');
516
- } finally {
517
- translateFullTextButton.disabled = false;
518
- translateFullTextButton.textContent = "Translate Full Text (PT)";
519
- }
520
- }
521
-
522
-
523
- // --- Activity Generation Functionality (NEW) ---
524
-
525
- async function generateActivity() {
526
- const activityType = activityTypeSelector.value;
527
- const context = contextSelector.value;
528
- const modelValue = modelSelector.value;
529
- const sourceText = getEditorText();
530
-
531
- generateActivityButton.disabled = true;
532
- activityText.textContent = "Generating...";
533
- activitySpinner.classList.remove('hidden');
534
- activityOutput.innerHTML = `<p class="text-gray-500 italic">Generating prompt...</p>`;
535
-
536
- let prompt = "";
537
- let title = "";
538
-
539
- if (activityType === 'translation_pt_en') {
540
- title = "Translation Exercise (PT to EN)";
541
- // We ask the AI to generate a Portuguese phrase first, based on the context, and then give the prompt.
542
- prompt = `You are a language teacher. Generate a single, challenging, but short Portuguese phrase that is perfectly relevant to the '${context}' context. Then, provide the Portuguese phrase to the user and prompt them to translate it to English. Format the output as: [PORTUGUESE PHRASE] \n\n Your Task: Translate the phrase above into English.`;
543
- } else if (activityType === 'email_writing') {
544
- title = "Formal Email Writing Prompt";
545
- prompt = `You are a language teacher. Generate a challenging, realistic scenario for a formal email writing exercise, based strictly on the '${context}' context. The prompt must be in English and clearly state the recipient (e.g., 'Your Manager'), the goal (e.g., 'Requesting a budget'), and the required tone (e.g., 'Formal and concise').`;
546
- } else if (activityType === 'summary_writing' && sourceText) {
547
- title = "Summarization Prompt";
548
- prompt = `You are a language teacher. Summarize the following English text in a single, complex English sentence, and then ask the user to expand your summary back into a paragraph, ensuring they use complex vocabulary related to the '${context}' context. The text to summarize is:\n\n---${sourceText}---`;
549
- } else {
550
- activityOutput.innerHTML = `<p class="text-red-500 font-semibold">Please enter text in the editor to use the Summarization Prompt.</p>`;
551
- generateActivityButton.disabled = false;
552
- activityText.textContent = "Generate Activity";
553
- activitySpinner.classList.add('hidden');
554
- return;
555
- }
556
-
557
- try {
558
- // Reusing explain-proxy for simple text generation
559
- const response = await fetch('/explain-proxy', {
560
- method: 'POST',
561
- headers: { 'Content-Type': 'application/json' },
562
- body: JSON.stringify({
563
- word: "ACTIVITY_GEN",
564
- context: context,
565
- for_flashcard: false,
566
- model: modelValue,
567
- custom_prompt: prompt
568
- })
569
- });
570
-
571
- if (!response.ok) {
572
- throw new Error(`HTTP Error: ${response.status}. Check API Keys.`);
573
- }
574
-
575
- const data = await response.json();
576
- // The explanation field holds the generated activity text
577
- const activityResult = data.explanation || data.translation || "Failed to generate activity prompt.";
578
-
579
- activityOutput.innerHTML = `<h3 class="font-bold mb-2">${title}</h3><div class="whitespace-pre-wrap">${activityResult}</div>`;
580
-
581
-
582
- } catch (error) {
583
- console.error('Error generating activity:', error);
584
- activityOutput.innerHTML = `<p class="text-red-500 font-semibold">Activity generation failed. Check if API Keys are configured correctly for ${modelValue}.</p>`;
585
- } finally {
586
  generateActivityButton.disabled = false;
587
- activityText.textContent = "Generate Activity";
588
  activitySpinner.classList.add('hidden');
589
- }
590
- }
591
-
592
-
593
- // --- Quick Translation Functionality (Double Click) ---
594
-
595
- async function fetchExplanationAndTranslation(word, context, isFlashcard=false) {
596
- const spinner = isFlashcard ? document.getElementById('cardSpinner') : document.getElementById('explanationSpinner');
597
- const explanationText = isFlashcard ? null : document.getElementById('explanationText');
598
- const translationText = isFlashcard ? null : document.getElementById('translationText');
599
-
600
- const modelValue = modelSelector.value;
601
- const contextFocus = contextSelector.value; // Pass context focus
602
-
603
- if (!isFlashcard) {
604
- explanationText.textContent = '';
605
- translationText.textContent = '';
606
- }
607
- spinner.classList.remove('hidden');
608
-
609
- try {
610
- const response = await fetch('/explain-proxy', {
611
- method: 'POST',
612
- headers: { 'Content-Type': 'application/json' },
613
- body: JSON.stringify({
614
- word: word,
615
- context: context,
616
- for_flashcard: isFlashcard,
617
- model: modelValue,
618
- context_focus: contextFocus // NEW: Pass context focus for accurate flashcards
619
- })
620
- });
621
-
622
- if (!response.ok) {
623
- const errorMsg = `HTTP Error: ${response.status}. Check if GEMINI_API_KEY/GROQ_API_KEY is configured in your Space for the selected model.`;
624
- throw new Error(errorMsg);
625
- }
626
-
627
- const data = await response.json();
628
-
629
- if (isFlashcard) {
630
- return data;
631
- } else {
632
- explanationText.textContent = data.explanation || 'Could not retrieve explanation.';
633
- translationText.textContent = data.translation || 'Could not retrieve translation.';
634
- }
635
- return data;
636
-
637
- } catch (error) {
638
- console.error('Error fetching context from AI:', error);
639
- const msg = `AI analysis failed. Error: ${error.message}`;
640
- if (!isFlashcard) {
641
- explanationText.textContent = msg;
642
- translationText.textContent = msg;
643
- } else {
644
- alert(msg);
645
- }
646
- return null;
647
- } finally {
648
- spinner.classList.add('hidden');
649
- }
650
- }
651
-
652
- // Logic to open the Modal on double click
653
- textEditor.addEventListener('dblclick', (event) => {
654
- const selection = window.getSelection().toString().trim();
655
- const selectedWord = selection.replace(/[^a-zA-Z']/g, ''); // Basic word selection logic
656
-
657
- if (selectedWord) {
658
- const fullText = getEditorText();
659
- const index = fullText.indexOf(selection);
660
- const context = fullText.substring(Math.max(0, index - 50), index + selection.length + 50);
661
-
662
- document.getElementById('wordSelected').textContent = selectedWord;
663
- openModal();
664
-
665
- fetchExplanationAndTranslation(selectedWord, context);
666
- }
667
- });
668
-
669
- // --- Flashcard Creation Functionality ---
670
-
671
- async function createFlashcardFromSelection() {
672
- const selectedText = window.getSelection().toString().trim();
673
- if (!selectedText) {
674
  return;
675
  }
676
-
677
- const fullText = getEditorText();
678
- const index = fullText.indexOf(selectedText);
679
- const context = fullText.substring(Math.max(0, index - 50), index + selectedText.length + 50);
680
-
681
- createCardButton.disabled = true;
682
- createCardButton.innerHTML = `<div class="spinner mr-2"></div> Creating Card...`;
683
-
684
- try {
685
- const cardData = await fetchExplanationAndTranslation(selectedText, context, true);
686
-
687
- if (cardData && cardData.term && cardData.translation && cardData.example) {
688
- renderFlashcard(cardData);
689
- } else {
690
- alert("The AI could not generate the structured card data. Please try another word or check the API key.");
691
- }
692
-
693
- } finally {
694
- createCardButton.disabled = false;
695
- createCardButton.innerHTML = `+ Create Flashcard from Selection`;
696
- }
697
- }
698
-
699
- function renderFlashcard(data) {
700
- noCardsMessage.classList.add('hidden');
701
-
702
- const cardElement = document.createElement('div');
703
- cardElement.className = 'flashcard';
704
-
705
- // Render card content
706
- cardElement.innerHTML = `
707
- <div class="flashcard-front">
708
- <p class="flashcard-term">${data.term.toUpperCase()}</p>
709
- <p class="flashcard-definition">PT: ${data.translation}</p>
710
- <p class="flashcard-example">Example: ${data.example}</p>
711
- </div>
712
- `;
713
-
714
- flashcardList.prepend(cardElement);
715
- }
716
-
717
-
718
- // --- Modal Control ---
719
-
720
- const modal = document.getElementById('explanationModal');
721
- const tabButtons = document.querySelectorAll('.tab-button');
722
- const tabContents = document.querySelectorAll('.tab-content');
723
-
724
- function openModal() {
725
- modal.style.display = 'block';
726
- showTab('explanation');
727
- }
728
-
729
- function closeModal() {
730
- modal.style.display = 'none';
731
- }
732
-
733
- function showTab(tabName) {
734
- tabContents.forEach(content => {
735
- content.classList.add('hidden');
736
- if (content.id === tabName + 'Tab') {
737
- content.classList.remove('hidden');
738
- }
739
- });
740
-
741
- tabButtons.forEach(button => {
742
- button.classList.remove('active');
743
- if (button.dataset.tab === tabName) {
744
- button.classList.add('active');
745
- }
746
  });
747
- }
748
 
749
- tabButtons.forEach(button => {
750
- button.addEventListener('click', () => {
751
- showTab(button.dataset.tab);
752
- });
753
- });
754
 
755
- // Close modal when clicking outside of it
756
- window.onclick = function(event) {
757
- if (event.target == modal) {
758
- closeModal();
759
- }
 
760
  }
761
- </script>
 
762
 
763
  </body>
764
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="pt-BR">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Dynamic English Study Studio</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
10
+ body { font-family: 'Inter', sans-serif; background-color: #f7f9fc; }
11
+ .card { background-color: white; border-radius: 12px; box-shadow: 0 4px_12px rgba(0,0,0,0.08); padding: 24px; }
12
+ .btn { padding: 0.5rem 1rem; border-radius: 8px; font-weight: 600; transition: all 0.2s; }
13
+ .btn-primary { background-color: #4f46e5; color: white; }
14
+ .btn-primary:hover { background-color: #4338ca; }
15
+ .btn-secondary { background-color: #e5e7eb; color: #1f2937; }
16
+ .btn-secondary:hover { background-color: #d1d5db; }
17
+ .btn-success { background-color: #10b981; color: white; }
18
+ .btn-success:hover { background-color: #059669; }
19
+ #textEditor { border: 1px solid #ccc; padding: 1rem; min-height: 16rem; border-radius: 0.5rem; outline: none; }
20
+ .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); backdrop-filter: blur(5px); }
21
+ .modal-content { background-color: #fff; margin: 10% auto; padding: 20px; border-radius: 10px; width: 90%; max-width: 500px; box-shadow: 0 8px 25px rgba(0,0,0,0.2); animation: slideIn 0.3s ease-out; }
22
+ @keyframes slideIn { from { opacity: 0; transform: translateY(-30px); } to { opacity: 1; transform: translateY(0); } }
23
+ .close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }
24
+ .spinner { border: 4px solid rgba(0, 0, 0, 0.1); width: 24px; height: 24px; border-radius: 50%; border-left-color: #4f46e5; animation: spin 1s ease infinite; }
25
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
26
+
27
+ /* --- ESTILOS DO FLASHCARD INTELIGENTE --- */
28
+ .flashcard-container { perspective: 1000px; margin-bottom: 1rem; }
29
+ .flashcard-inner { position: relative; width: 100%; height: 100%; transition: transform 0.6s; transform-style: preserve-3d; }
30
+ .flashcard-container.flipped .flashcard-inner { transform: rotateY(180deg); }
31
+ .flashcard-front, .flashcard-back { position: absolute; width: 100%; backface-visibility: hidden; -webkit-backface-visibility: hidden; border: 1px solid #e5e7eb; border-radius: 12px; padding: 20px; min-height: 280px; display: flex; flex-direction: column; justify-content: center; }
32
+ .flashcard-front { background-color: #f9fafb; }
33
+ .flashcard-back { background-color: #ffffff; transform: rotateY(180deg); }
34
+ .flashcard-container .label { font-size: 0.8rem; color: #6b7280; font-weight: 600; margin-bottom: 0.5rem; }
35
+ .flashcard-container .context-phrase { font-size: 1.1rem; color: #1f2937; text-align: center; margin-bottom: 1rem; line-height: 1.6; }
36
+ .flashcard-container .hint { font-size: 1rem; color: #4f46e5; font-weight: 500; text-align: center; border-top: 1px dashed #d1d5db; padding-top: 1rem; margin-top: 1rem; }
37
+ .flashcard-back .term { font-size: 1.75rem; font-weight: 700; color: #4f46e5; text-align: center; margin-bottom: 1rem; }
38
+ .flashcard-back .full-sentence { display: flex; align-items: center; gap: 0.5rem; font-style: italic; color: #4b5563; margin-bottom: 1rem; }
39
+ .flashcard-back .definition { font-size: 0.9rem; color: #374151; margin-bottom: 1.5rem; }
40
+ .audio-btn { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #4f46e5; }
41
+ .srs-buttons { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; margin-top: auto; }
42
+ .srs-btn { width: 100%; border: 1px solid; padding: 0.6rem 0.5rem; font-size: 0.8rem; font-weight: 600; border-radius: 6px; cursor: pointer; transition: all 0.2s; }
43
+ .srs-again { border-color: #ef4444; color: #ef4444; } .srs-again:hover { background-color: #fef2f2; }
44
+ .srs-easy { border-color: #22c55e; color: #22c55e; } .srs-easy:hover { background-color: #f0fdf4; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  </style>
46
  </head>
47
+ <body class="p-4 md:p-8">
48
 
49
+ <div class="container mx-auto max-w-5xl">
50
  <header class="text-center mb-10">
51
+ <h1 class="text-3xl lg:text-4xl font-bold text-gray-800">Dynamic English Study Studio</h1>
52
+ <p class="text-gray-500 mt-2">Sua ferramenta de estudo com áudio, contexto e flashcards inteligentes.</p>
53
  </header>
54
 
55
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
56
+ <!-- Coluna da Esquerda: Texto e Controles -->
57
+ <div class="flex flex-col gap-6">
58
+ <div class="card">
59
+ <h2 class="text-xl font-semibold mb-4 text-gray-700">Seu Texto de Estudo</h2>
60
+ <div id="textEditor" contenteditable="true" spellcheck="true" lang="en" class="w-full p-4 focus:ring-2 focus:ring-indigo-500 text-gray-800"></div>
61
+ <p class="text-sm text-right text-gray-500 mt-2">Caracteres: <span id="charCount">0</span>/5000</p>
62
+ <div class="mt-4 border-t pt-4">
63
+ <button id="createCardButton" onclick="createFlashcardFromSelection()" class="btn btn-success disabled:opacity-50" disabled>+ Criar Flashcard da Seleção</button>
64
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  </div>
66
 
67
+ <div class="card">
68
+ <h2 class="text-xl font-semibold mb-4 text-gray-700">Controles de LLM e Áudio</h2>
69
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
70
+ <div>
71
+ <label for="modelSelector" class="block text-sm font-medium text-gray-700">Provedor de IA</label>
72
+ <select id="modelSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
73
+ <option value="gemini:gemini-1.5-flash">Google Gemini 1.5 Flash</option>
74
+ <option value="groq:mixtral-8x7b-32768">Groq (Mixtral 8x7b)</option>
75
+ <option value="groq:llama3-8b-8192">Groq (Llama 3 8B)</option>
76
+ </select>
77
+ </div>
78
+ <div>
79
+ <label for="contextSelector" class="block text-sm font-medium text-gray-700">Foco do Vocabulário</label>
80
+ <select id="contextSelector" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 rounded-md shadow-sm">
81
+ <option value="General/Social (Casual conversation, daily life)">Geral/Social</option>
82
+ <option value="Professional/Business (Meetings, emails, reports)">Profissional/Negócios</option>
83
+ <option value="Technical/IT (Software, Engineering, Tech topics)">Técnico/TI</option>
84
+ </select>
85
+ </div>
86
+ </div>
87
+ <div class="mt-4">
88
+ <audio id="audioPlayer" controls class="w-full"></audio>
89
+ </div>
 
 
 
 
 
90
  </div>
91
+ <div class="card">
92
+ <h2 class="text-xl font-semibold mb-4 text-gray-700">Atividades de Escrita e Tradução</h2>
93
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
94
+ <div class="md:col-span-2">
95
+ <label for="activityTypeSelector" class="block text-sm font-medium text-gray-700">Tipo de Atividade</label>
96
+ <select id="activityTypeSelector" class="mt-1 block w-full rounded-md shadow-sm">
97
+ <option value="translation_pt_en">Tradução de PT para EN</option>
98
+ <option value="email_writing">Escrever Email Formal</option>
99
+ <option value="summary_writing">Resumir Texto</option>
100
+ </select>
101
+ </div>
102
+ <div class="md:col-span-1 flex items-end">
103
+ <button id="generateActivityButton" onclick="generateActivity()" class="btn btn-primary w-full py-2.5 flex items-center justify-center">
104
+ <span id="activityText">Gerar</span>
105
+ <div id="activitySpinner" class="spinner ml-3 hidden"></div>
106
+ </button>
107
+ </div>
108
+ </div>
109
+ <div id="activityOutput" class="border p-4 mt-4 rounded-lg bg-gray-50 min-h-[100px]">
110
+ <p class="text-gray-500 italic">Selecione uma atividade para gerar um exercício.</p>
111
+ </div>
112
  </div>
113
  </div>
114
 
115
+ <!-- Coluna da Direita: Flashcards -->
116
+ <div id="flashcardsPanel" class="card">
117
+ <h2 class="text-xl font-semibold mb-4 text-gray-700">Sessão de Estudo: Flashcards</h2>
118
+ <div id="flashcardList" class="space-y-4">
119
+ <p id="noCardsMessage" class="text-gray-500 italic">Nenhum flashcard criado ainda. Selecione uma palavra ou frase no texto e clique em 'Criar Flashcard'.</p>
 
 
 
 
 
 
120
  </div>
121
  </div>
122
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  </div>
124
 
125
+ <!-- Modal (Clique Duplo) -->
126
  <div id="explanationModal" class="modal">
127
+ <!-- Conteúdo do modal permanece o mesmo -->
128
+ </div>
 
129
 
130
+ <script>
131
+ // --- ELEMENTOS GLOBAIS ---
132
+ const textEditor = document.getElementById('textEditor');
133
+ const createCardButton = document.getElementById('createCardButton');
134
+ const flashcardList = document.getElementById('flashcardList');
135
+ const noCardsMessage = document.getElementById('noCardsMessage');
136
+ const modelSelector = document.getElementById('modelSelector');
137
+ const contextSelector = document.getElementById('contextSelector');
138
+ // Mapeamento dos elementos de atividade
139
+ const activityTypeSelector = document.getElementById('activityTypeSelector');
140
+ const generateActivityButton = document.getElementById('generateActivityButton');
141
+ const activityOutput = document.getElementById('activityOutput');
142
+ const activitySpinner = document.getElementById('activitySpinner');
143
+ const activityText = document.getElementById('activityText');
144
+
145
+ // --- LÓGICA DO EDITOR E SELEÇÃO ---
146
+ textEditor.addEventListener('mouseup', () => {
147
+ const selection = window.getSelection().toString().trim();
148
+ createCardButton.disabled = !selection;
149
+ });
150
+
151
+ // --- GERAÇÃO DE FLASHCARD INTELIGENTE ---
152
+ async function createFlashcardFromSelection() {
153
+ const selectedText = window.getSelection().toString().trim();
154
+ if (!selectedText) return;
155
+
156
+ const fullText = textEditor.innerText;
157
+
158
+ createCardButton.disabled = true;
159
+ createCardButton.innerHTML = `<div class="spinner mr-2" style="width:16px; height:16px; border-width:2px;"></div> Criando...`;
160
+
161
+ try {
162
+ const response = await fetch('/explain-proxy', {
163
+ method: 'POST',
164
+ headers: { 'Content-Type': 'application/json' },
165
+ body: JSON.stringify({
166
+ word: selectedText,
167
+ context: fullText,
168
+ for_flashcard: true,
169
+ model: modelSelector.value,
170
+ context_focus: contextSelector.value
171
+ })
172
+ });
173
 
174
+ if (!response.ok) {
175
+ const errorData = await response.json();
176
+ throw new Error(errorData.error || `HTTP Error: ${response.status}`);
177
+ }
178
+ const cardData = await response.json();
179
+ renderFlashcard(cardData);
180
+ } catch (error) {
181
+ alert(`Falha ao criar flashcard: ${error.message}`);
182
+ } finally {
183
+ createCardButton.disabled = false;
184
+ createCardButton.textContent = '+ Criar Flashcard da Seleção';
185
+ }
186
+ }
187
+
188
+ function renderFlashcard(data) {
189
+ noCardsMessage.classList.add('hidden');
190
+
191
+ const cardId = `card-${Date.now()}`;
192
+ const container = document.createElement('div');
193
+ container.className = 'flashcard-container';
194
+ container.id = cardId;
195
+ container.style.minHeight = '280px'; // Garante altura mínima
196
+
197
+ container.innerHTML = `
198
+ <div class="flashcard-inner">
199
+ <!-- FRENTE DO CARD -->
200
+ <div class="flashcard-front">
201
+ <div class="label">Frase de Contexto:</div>
202
+ <p class="context-phrase">${data.gapped_sentence}</p>
203
+ <div class="hint">
204
+ <strong>Dica (PT):</strong> ${data.translation}
205
  </div>
206
+ <div class="text-center text-sm text-gray-400 mt-4">Clique para virar</div>
207
  </div>
208
+ <!-- VERSO DO CARD -->
209
+ <div class="flashcard-back">
210
+ <h3 class="term">${data.term}</h3>
211
+ <p class="full-sentence">
212
+ <button class="audio-btn" onclick="playWord(this, '${data.term.replace(/'/g, "\\'")}')">▶️</button>
213
+ <span>${data.context_sentence}</span>
214
+ </p>
215
+ <p class="definition"><strong>Definition:</strong> ${data.definition}</p>
216
+ <div class="text-center mt-4">
217
+ <button class="btn btn-secondary text-sm" disabled>🎙️ Pratique (em breve)</button>
218
+ </div>
219
+ <div class="srs-buttons mt-4">
220
+ <button class="srs-btn srs-again" onclick="handleSrsClick('${cardId}', 'again')">Errei</button>
221
+ <button class="srs-btn srs-easy" onclick="handleSrsClick('${cardId}', 'easy')">Fácil</button>
222
  </div>
223
  </div>
224
  </div>
225
+ `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ flashcardList.prepend(container);
228
+ container.addEventListener('click', () => {
229
+ container.classList.toggle('flipped');
 
 
 
 
 
 
 
 
230
  });
231
+ }
232
+
233
+ // --- FUNÇÕES DE INTERATIVIDADE DO FLASHCARD ---
234
+
235
+ async function playWord(button, word) {
236
+ event.stopPropagation(); // Impede que o clique no botão vire o card
237
+ const originalContent = button.innerHTML;
238
+ button.innerHTML = '🔊';
239
+ button.disabled = true;
240
+
241
+ try {
242
+ const response = await fetch('/tts-proxy', {
243
+ method: 'POST',
244
+ headers: { 'Content-Type': 'application/json' },
245
+ body: JSON.stringify({ text: word })
246
+ });
247
+ if (!response.ok) throw new Error('Falha ao gerar áudio.');
248
+
249
+ const audioBlob = await response.blob();
250
+ const audioUrl = URL.createObjectURL(audioBlob);
251
+ const audio = new Audio(audioUrl);
252
+ audio.play();
253
+ audio.onended = () => URL.revokeObjectURL(audioUrl);
254
+
255
+ } catch (error) {
256
+ console.error('Erro ao tocar áudio:', error);
257
+ } finally {
258
+ setTimeout(() => {
259
+ button.innerHTML = originalContent;
260
+ button.disabled = false;
261
+ }, 1000);
262
+ }
263
+ }
264
+
265
+ function handleSrsClick(cardId, level) {
266
+ event.stopPropagation(); // Impede que o clique no botão vire o card
267
+ const cardContainer = document.getElementById(cardId);
268
+ cardContainer.style.transition = 'opacity 0.5s';
269
+ cardContainer.style.opacity = '0.2';
270
+ console.log(`Card '${cardId}' avaliado como '${level}'. A lógica de agendamento será implementada aqui.`);
271
+ alert(`Flashcard marcado como "${level}". Em um sistema completo, ele seria reagendado!`);
272
+ }
273
+
274
+ // --- LÓGICA DE GERAÇÃO DE ATIVIDADES ---
275
+ async function generateActivity() {
276
+ const activityType = activityTypeSelector.value;
277
+ const context = contextSelector.value;
278
+ const modelValue = modelSelector.value;
279
+ const sourceText = textEditor.innerText;
280
+
281
+ generateActivityButton.disabled = true;
282
+ activityText.textContent = "";
283
+ activitySpinner.classList.remove('hidden');
284
+ activityOutput.innerHTML = `<p class="text-gray-500 italic">Gerando exercício... O modelo está pensando!</p>`;
285
+
286
+ let prompt = "";
287
+ let title = "";
288
+
289
+ if (activityType === 'translation_pt_en') {
290
+ title = "Exercício de Tradução (PT para EN)";
291
+ prompt = `Gere uma única frase curta em português, relevante para o contexto '${context}', para o usuário traduzir para o inglês. Apresente apenas a frase em português.`;
292
+ } else if (activityType === 'email_writing') {
293
+ title = "Exercício de Escrita de Email";
294
+ prompt = `Gere um cenário realista para um email formal em inglês, baseado no contexto '${context}'. O prompt deve ser claro, indicando o objetivo e o destinatário.`;
295
+ } else if (activityType === 'summary_writing') {
296
+ if (!sourceText.trim()) {
297
+ alert("Por favor, insira um texto no editor para a atividade de resumo.");
298
+ // Reset button state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  generateActivityButton.disabled = false;
300
+ activityText.textContent = "Gerar";
301
  activitySpinner.classList.add('hidden');
302
+ activityOutput.innerHTML = `<p class="text-gray-500 italic">Selecione uma atividade para gerar um exercício.</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  return;
304
  }
305
+ title = "Exercício de Resumo";
306
+ prompt = `Baseado no texto a seguir, peça ao usuário para resumi-lo em uma ou duas sentenças em inglês. Texto: ---${sourceText}---`;
307
+ }
308
+
309
+ try {
310
+ const response = await fetch('/explain-proxy', {
311
+ method: 'POST',
312
+ headers: { 'Content-Type': 'application/json' },
313
+ body: JSON.stringify({
314
+ model: modelValue,
315
+ context_focus: context,
316
+ custom_prompt: prompt
317
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  });
319
+ if (!response.ok) throw new Error('Falha na comunicação com a IA.');
320
 
321
+ const data = await response.json();
322
+ const result = data.explanation.replace(/\n/g, '<br>');
323
+ activityOutput.innerHTML = `<h3 class="font-bold mb-2">${title}</h3><div>${result}</div>`;
 
 
324
 
325
+ } catch (error) {
326
+ activityOutput.innerHTML = `<p class="text-red-500 font-semibold">Falha ao gerar atividade. Verifique suas chaves de API. Erro: ${error.message}</p>`;
327
+ } finally {
328
+ generateActivityButton.disabled = false;
329
+ activityText.textContent = "Gerar";
330
+ activitySpinner.classList.add('hidden');
331
  }
332
+ }
333
+ </script>
334
 
335
  </body>
336
  </html>