document.addEventListener('DOMContentLoaded', () => { const editor = document.getElementById('editor'); const suggestionOverlay = document.getElementById('suggestion-overlay'); const status = document.getElementById('status'); // Controls const nGramSelect = document.getElementById('n-gram'); 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; const autoResize = () => { editor.style.height = 'auto'; suggestionOverlay.style.height = 'auto'; const newHeight = Math.max(360, editor.scrollHeight); editor.style.height = newHeight + 'px'; suggestionOverlay.style.height = newHeight + 'px'; }; tempInput.addEventListener('input', () => { tempValDisplay.textContent = tempInput.value; }); lengthInput.addEventListener('input', () => { lengthValDisplay.textContent = lengthInput.value; }); const triggerUpdate = () => { currentSuggestion = ''; updateSuggestion(); const prompt = editor.value; if (prompt.trim().length > 0) fetchPrediction(prompt); }; nGramSelect.addEventListener('change', triggerUpdate); tempInput.addEventListener('change', triggerUpdate); lengthInput.addEventListener('change', triggerUpdate); const fetchPrediction = async (prompt, customLength = null) => { if (isFetching) return; isFetching = true; status.textContent = 'Thinking...'; status.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'; status.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(); autoResize(); window.scrollTo(0, document.body.scrollHeight); }; editor.addEventListener('input', () => { autoResize(); 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); }); autoResize(); });