document.addEventListener('DOMContentLoaded', () => { const editor = document.getElementById('editor'); const suggestionOverlay = document.getElementById('suggestion-overlay'); const status = document.getElementById('status'); const statusIndicator = document.querySelector('.status-indicator'); // Controls const nGramSelect = document.getElementById('n-gram'); const nValDisplay = document.getElementById('n-val'); const tempInput = document.getElementById('temperature'); const tempValDisplay = document.getElementById('temp-val'); const lengthInput = document.getElementById('length'); const lengthValDisplay = document.getElementById('length-val'); const generateBtn = document.getElementById('generate-more-btn'); let currentSuggestion = ''; let isFetching = false; let debounceTimer; // --- UI Logic --- const updateUI = () => { nValDisplay.textContent = nGramSelect.value; tempValDisplay.textContent = tempInput.value; lengthValDisplay.textContent = lengthInput.value; }; tempInput.addEventListener('input', updateUI); lengthInput.addEventListener('input', updateUI); nGramSelect.addEventListener('change', () => { updateUI(); triggerUpdate(); }); const triggerUpdate = () => { currentSuggestion = ''; updateSuggestion(); const prompt = editor.value; if (prompt.trim().length > 0) fetchPrediction(prompt); }; tempInput.addEventListener('change', triggerUpdate); lengthInput.addEventListener('change', triggerUpdate); // --- Core Functions --- const fetchPrediction = async (prompt, customLength = null) => { if (isFetching) return; isFetching = true; status.textContent = 'Thinking...'; statusIndicator.classList.add('fetching'); const n = parseInt(nGramSelect.value); const temperature = parseFloat(tempInput.value); const length = customLength || parseInt(lengthInput.value); try { const response = await fetch('/api/predict', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt, n, temperature, length }), }); if (!response.ok) throw new Error('Network response failed'); const data = await response.json(); if (customLength) { insertText(data.prediction || ''); } else { currentSuggestion = data.prediction || ''; updateSuggestion(); } } catch (error) { console.error('Prediction failed:', error); status.textContent = 'Error'; } finally { isFetching = false; status.textContent = 'Idle'; statusIndicator.classList.remove('fetching'); } }; const updateSuggestion = () => { const editorText = editor.value; const space = (editorText.length > 0 && !/\s$/.test(editorText)) ? ' ' : ''; suggestionOverlay.textContent = editorText + space + currentSuggestion; }; const insertText = (text) => { if (!text) return; const space = (editor.value.length > 0 && !/\s$/.test(editor.value)) ? ' ' : ''; editor.value += space + text; currentSuggestion = ''; updateSuggestion(); // Ensure the editor scrolls with content editor.scrollTop = editor.scrollHeight; }; // --- Event Handlers --- editor.addEventListener('input', () => { clearTimeout(debounceTimer); currentSuggestion = ''; updateSuggestion(); const prompt = editor.value; if (prompt.trim().length === 0) return; debounceTimer = setTimeout(() => fetchPrediction(prompt), 300); }); editor.addEventListener('keydown', (e) => { if (e.key === 'Tab' && currentSuggestion) { e.preventDefault(); insertText(currentSuggestion); fetchPrediction(editor.value); } }); generateBtn.addEventListener('click', () => { fetchPrediction(editor.value, 50); }); // Sync scroll editor.addEventListener('scroll', () => { suggestionOverlay.scrollTop = editor.scrollTop; }); // Initialize UI badges updateUI(); });