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"); const sidebarToggle = document.getElementById("sidebar-toggle"); const sidebar = document.getElementById("sidebar"); const acceptSuggestionBtn = document.getElementById("accept-suggestion-btn"); let currentSuggestion = ""; let isFetching = false; let debounceTimer; // --- UI Logic --- const updateUI = () => { nValDisplay.textContent = nGramSelect.value; tempValDisplay.textContent = tempInput.value; lengthValDisplay.textContent = lengthInput.value; }; sidebarToggle.addEventListener("click", () => { sidebar.classList.toggle("open"); }); const closeSidebarOnMobile = () => { if (window.innerWidth <= 768) { sidebar.classList.remove("open"); } }; 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; // Show/hide accept button if (currentSuggestion) { acceptSuggestionBtn.classList.add("visible"); } else { acceptSuggestionBtn.classList.remove("visible"); } }; 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); } }); acceptSuggestionBtn.addEventListener("click", () => { if (currentSuggestion) { insertText(currentSuggestion); fetchPrediction(editor.value); editor.focus(); } }); generateBtn.addEventListener("click", () => { fetchPrediction(editor.value, 50); closeSidebarOnMobile(); }); // Sync scroll - FIX: Use transform instead of scrollTop editor.addEventListener("scroll", () => { suggestionOverlay.style.transform = `translateY(-${editor.scrollTop}px)`; }); // Initialize UI badges updateUI(); });