PageSourceSearch

https://diccionario-moderno-2--rivera.on.websim.com/video-player-main.js

js diccionario-moderno-2--rivera.on.websim.com collected 2026-10-03 05:06:19 UTC 10,345 bytes, 306 lines download raw bytes

1import { VideoPlayerUI } from './video-player-ui.js';
2import { TTSPlayer } from './video-player-tts.js';
3import { 
4    generateVideoScript, 
5    generateElaborationScript,
6    generateComparisonVideoScript,
7    generateThematicVideoScript
8} from './video-script-generator.js';
9import { fetchClosestSynonyms } from './api.js';
10
11const videoBtn = document.querySelector('.video-btn');
12const searchInput = document.querySelector('.search-input');
13
14const ui = new VideoPlayerUI();
15const tts = new TTSPlayer();
16
17let currentScript = {
18    rawScript: null,
19    title: '',
20    chapters: [],
21    sentencesForDisplay: [],
22    sentencesForTTS: [],
23    sentenceToChapterMap: [],
24    exploreNext: [], // Add exploreNext to script data
25};
26
27let generationState = {
28    word: '',
29    type: '', // 'explanation', 'comparison', 'thematic'
30    comparisonWords: []
31};
32
33// --- Link UI events to TTS player ---
34ui.on('play', () => {
35    if (tts.isPaused()) {
36        tts.resume();
37    } else {
38        ui.hideEndScreen(); // Hide end screen on replay
39        tts.play(currentScript.sentencesForTTS);
40    }
41});
42
43ui.on('pause', () => tts.pause());
44ui.on('stop', () => tts.stop());
45ui.on('close', () => {
46    tts.stop();
47    ui.closeModal();
48});
49ui.on('replay', () => {
50    ui.hideEndScreen();
51    tts.play(currentScript.sentencesForTTS);
52});
53
54ui.on('scriptSelect', async ({ type }) => {
55    generationState.type = type;
56
57    if (type === 'comparison') {
58        ui.showComparisonChoice(generationState.word);
59    } else {
60        runScriptGeneration();
61    }
62});
63
64ui.on('comparisonChoice', async ({ userSelectsWords }) => {
65    if (userSelectsWords) {
66        const synonyms = await fetchClosestSynonyms(generationState.word, 5);
67        // Filter out the original word from suggestions, case-insensitive
68        const filteredSynonyms = synonyms.filter(s => s.toLowerCase() !== generationState.word.toLowerCase());
69        ui.showComparisonInput(filteredSynonyms);
70    } else {
71        // User wants AI to choose, so we run generation with empty comparison words.
72        generationState.comparisonWords = [];
73        runScriptGeneration();
74    }
75});
76
77ui.on('comparisonSubmit', ({ words }) => {
78    generationState.comparisonWords = words;
79    runScriptGeneration();
80});
81
82ui.on('seek', (percentage) => {
83    if (currentScript.sentencesForTTS.length > 0) {
84        const totalSentences = currentScript.sentencesForTTS.length;
85        // Ensure index is within bounds
86        let index = Math.floor(percentage * totalSentences);
87        index = Math.max(0, Math.min(index, totalSentences - 1));
88        
89        tts.jumpTo(index);
90        ui.hideEndScreen();
91    }
92});
93
94ui.on('exploreNext', (word) => {
95    ui.closeModal();
96    tts.stop();
97    searchInput.value = word;
98    searchInput.focus();
99    // Optional: could trigger a search here, but for now just populates the input
100    // fetchWord(word); 
101});
102
103ui.on('copy', () => {
104    const fullScriptText = currentScript.sentencesForTTS.join('\n\n');
105    navigator.clipboard.writeText(fullScriptText).catch(err => {
106        console.error("No se pudo copiar el guion:", err);
107        // Optionally show an error to the user
108    });
109});
110
111ui.on('jumpToSentence', (index) => {
112    tts.jumpTo(index);
113    ui.hideEndScreen(); // If user jumps from end screen, hide it.
114});
115
116ui.on('jumpToChapter', (chapterIndex) => {
117    if (currentScript.sentenceToChapterMap) {
118        const sentenceIndex = currentScript.sentenceToChapterMap.findIndex(
119            ci => ci === chapterIndex
120        );
121        if (sentenceIndex !== -1) {
122            tts.jumpTo(sentenceIndex);
123            ui.hideEndScreen(); // If user jumps from end screen, hide it.
124        }
125    }
126});
127
128ui.on('elaborate', async () => {
129    if (!currentScript.rawScript) {
130        ui.showError("No se puede elaborar, faltan los datos del guion original.");
131        return;
132    }
133
134    ui.hideEndScreen();
135    ui.showLoading(true, "Profundizando... Elaborando el guion...");
136
137    try {
138        const newRawScript = await generateElaborationScript(currentScript.rawScript);
139        processAndPlayScript(newRawScript);
140    } catch (error) {
141        console.error("Failed to generate elaborated script:", error);
142        ui.showLoading(false);
143        ui.showError("La musa de la IA está durmiendo. No se pudo elaborar.");
144    }
145});
146
147// --- Link TTS events to UI updates ---
148tts.on('sentencechange', ({ sentence, index }) => {
149    const displaySentence = currentScript.sentencesForDisplay[index];
150    ui.updateTeleprompter(displaySentence);
151    ui.highlightSentenceInPanel(index);
152
153    const chapterIndex = currentScript.sentenceToChapterMap[index];
154    if (chapterIndex !== undefined) {
155        ui.highlightChapter(chapterIndex);
156    }
157    
158    ui.updateProgress(index + 1, currentScript.sentencesForTTS.length);
159});
160
161tts.on('statechange', (state) => {
162    ui.setPlayerState(state); // 'playing', 'paused', 'stopped'
163});
164
165tts.on('error', (errorMessage) => {
166    ui.showError(errorMessage);
167});
168
169tts.on('ended', () => {
170    ui.showEndScreen();
171});
172
173// --- Sentence Splitter Helper ---
174/**
175 * A more robust sentence splitter.
176 * It splits based on periods, question marks, exclamation marks, and newlines.
177 * It handles cases where multiple punctuation marks appear together and avoids creating empty sentences.
178 * It also tries to keep sentences with abbreviations (like "e.g.") together.
179 * @param {string} text - The text content to split.
180 * @returns {string[]} An array of sentences.
181 */
182function splitIntoSentences(text) {
183    if (!text || typeof text !== 'string') return [];
184
185    // Normalize newlines and clean up whitespace
186    text = text.replace(/(\r\n|\n|\r)/gm, ". ").replace(/\s+/g, ' ');
187
188    // This regex looks for sentence-ending punctuation (.!?) followed by a space and an uppercase letter, or the end of the string.
189    // It's designed to be conservative to avoid splitting in the middle of sentences.
190    // Updated for Spanish uppercase letters after punctuation.
191    const sentences = text.match(/[^.!?]+[.!?]+(\s(?=[A-ZÁÉÍÓÚÜÑ])|$|(?=\"))/g);
192
193    if (!sentences) {
194        // If the regex fails (e.g., single sentence with no punctuation), return the whole text if it's not empty.
195        const trimmedText = text.trim();
196        return trimmedText ? [trimmedText] : [];
197    }
198    
199    return sentences
200        .map(s => s.trim())
201        .filter(s => s && s.length > 1 && /[a-zA-Z]/.test(s)); // Filter out empty strings, single characters, and non-alphabetic fragments.
202}
203
204async function runScriptGeneration() {
205    ui.showLoading(true, "Invocando al genio creativo...");
206    let rawScript;
207
208    try {
209        switch (generationState.type) {
210            case 'explanation':
211                rawScript = await generateVideoScript(generationState.word);
212                break;
213            case 'comparison':
214                rawScript = await generateComparisonVideoScript(generationState.word, generationState.comparisonWords);
215                break;
216            case 'thematic':
217                rawScript = await generateThematicVideoScript(generationState.word);
218                break;
219            default:
220                throw new Error("Invalid script type selected.");
221        }
222        
223        processAndPlayScript(rawScript);
224
225    } catch (error) {
226        console.error("Failed to generate or display video script:", error);
227        ui.showLoading(false);
228        ui.showError("¡Ups! El guionista de IA está en su descanso para el café. Por favor, inténtalo de nuevo.");
229    }
230}
231
232function processAndPlayScript(rawScript) {
233     if (!rawScript || !rawScript.chapters || !rawScript.chapters.length) {
234        throw new Error("El guion generado estaba vacío o no era válido.");
235    }
236    
237    // Process script: flatten sentences, map to chapters, handle formatting
238    let flatSentencesForDisplay = [];
239    let flatSentencesForTTS = [];
240    let sentenceToChapterMap = [];
241
242    rawScript.chapters.forEach((chapter, chapterIndex) => {
243        // Use the new, more robust sentence splitter
244        const chapterSentences = splitIntoSentences(chapter.content);
245        
246        chapterSentences.forEach(sentence => {
247            // For display: convert asterisks to <em> tags
248            const displaySentence = sentence.replace(/\*(.*?)\*/g, '<em>$1</em>');
249            // For TTS: strip asterisks completely
250            const ttsSentence = sentence.replace(/\*/g, '');
251            
252            flatSentencesForDisplay.push(displaySentence);
253            flatSentencesForTTS.push(ttsSentence);
254            sentenceToChapterMap.push(chapterIndex);
255        });
256    });
257
258    if (flatSentencesForTTS.length === 0) {
259         throw new Error("El procesamiento del guion no resultó en frases válidas.");
260    }
261
262    currentScript = {
263        rawScript: rawScript,
264        title: rawScript.title,
265        chapters: rawScript.chapters.map(c => c.title),
266        sentencesForDisplay: flatSentencesForDisplay,
267        sentencesForTTS: flatSentencesForTTS,
268        sentenceToChapterMap: sentenceToChapterMap,
269        exploreNext: rawScript.exploreNext || [],
270    };
271
272    ui.showLoading(false);
273    ui.setScript(currentScript.title, currentScript.sentencesForDisplay, currentScript.chapters, currentScript.exploreNext);
274    
275    // Add to history after successful generation and processing
276    window.addToHistory(generationState.word, generationState.type);
277
278    // Auto-play after loading, only if the script is valid.
279    if (currentScript.sentencesForTTS && currentScript.sentencesForTTS.length > 0) {
280        setTimeout(() => {
281            if (tts.isStopped()) {
282                 tts.play(currentScript.sentencesForTTS);
283            }
284        }, 500);
285    } else {
286        // This is a fallback case, though the earlier check should catch it.
287        throw new Error("No se puede reproducir el video: El guion no tiene frases que se puedan verbalizar.");
288    }
289}
290
291// --- Main Button Action ---
292videoBtn.addEventListener('click', async () => {
293    const query = searchInput.value.trim();
294    if (!query) {
295        searchInput.placeholder = "¡Por favor, introduce una palabra primero!";
296        searchInput.focus();
297        setTimeout(() => {
298            searchInput.placeholder = "Escribe una palabra...";
299        }, 2000);
300        return;
301    }
302
303    generationState.word = query;
304    ui.openModal();
305    ui.showScriptSelection(query);
306});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.