/* app.js — GPT-2 Playground ========================== Handles all client-side behaviour: 1. Slider sync — live-update value badges next to each range input 2. Char counter — updates as the user types in the prompt textarea 3. Generation flow — POSTs to /generate, manages loading state on the button, renders prompt + generated text in the output pane 4. Copy button — copies the full output text and shows brief confirmation 5. Error display — surfaces API or network errors in the error banner No build step or framework required — plain ES2020, runs in any modern browser. */ const $ = id => document.getElementById(id); // ── Slider → value badge sync ────────────────────────────────────────────── const sliders = [ { input: $("maxTokens"), display: $("maxTokensVal"), decimals: 0 }, { input: $("temperature"), display: $("temperatureVal"), decimals: 2 }, { input: $("topP"), display: $("topPVal"), decimals: 2 }, { input: $("repPenalty"), display: $("repPenaltyVal"), decimals: 2 }, ]; function syncSlider({ input, display, decimals }) { display.textContent = parseFloat(input.value).toFixed(decimals); } sliders.forEach(s => { syncSlider(s); s.input.addEventListener("input", () => syncSlider(s)); }); // ── Character counter ────────────────────────────────────────────────────── const promptInput = $("promptInput"); const charCount = $("charCount"); promptInput.addEventListener("input", () => { charCount.textContent = `${promptInput.value.length} chars`; }); // ── Generation ───────────────────────────────────────────────────────────── const generateBtn = $("generateBtn"); const outputPrompt = $("outputPrompt"); const outputGenerated = $("outputGenerated"); const errorBanner = $("errorBanner"); const copyBtn = $("copyBtn"); function setLoading(isLoading) { generateBtn.disabled = isLoading; generateBtn.classList.toggle("is-loading", isLoading); // Apply the wider gradient background only during load so shimmer looks right if (isLoading) { generateBtn.style.backgroundImage = "linear-gradient(90deg,#7c3aed,#6d28d9,#2563eb,#4f46e5,#7c3aed)"; generateBtn.style.backgroundSize = "300% 100%"; generateBtn.querySelector(".btn-label").textContent = "Generating…"; } else { generateBtn.style.backgroundImage = ""; generateBtn.style.backgroundSize = ""; generateBtn.querySelector(".btn-label").textContent = "Generate"; } } function showError(message) { errorBanner.textContent = message; errorBanner.hidden = false; } function clearError() { errorBanner.hidden = true; errorBanner.textContent = ""; } function renderOutput(prompt, generatedText) { outputPrompt.textContent = prompt; outputGenerated.textContent = generatedText; outputGenerated.innerHTML = ""; // clear placeholder span outputGenerated.textContent = generatedText; copyBtn.disabled = false; } generateBtn.addEventListener("click", async () => { const prompt = promptInput.value.trim(); if (!prompt) { showError("Please enter a prompt before generating."); return; } clearError(); setLoading(true); copyBtn.disabled = true; outputPrompt.textContent = ""; outputGenerated.innerHTML = 'Generating…'; const payload = { prompt, max_new_tokens: parseInt($("maxTokens").value, 10), temperature: parseFloat($("temperature").value), top_p: parseFloat($("topP").value), repetition_penalty: parseFloat($("repPenalty").value), }; try { const response = await fetch("/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!response.ok) { const err = await response.json().catch(() => ({})); throw new Error(err.error || `Server error ${response.status}`); } const data = await response.json(); renderOutput(data.prompt, data.generated_text); } catch (err) { showError(`Generation failed: ${err.message}`); outputGenerated.innerHTML = 'Generated text will appear here…'; } finally { setLoading(false); } }); // ── Copy button ───────────────────────────────────────────────────────────── copyBtn.addEventListener("click", () => { const fullText = (outputPrompt.textContent + outputGenerated.textContent).trim(); if (!fullText) return; navigator.clipboard.writeText(fullText).then(() => { copyBtn.textContent = "✓ Copied!"; copyBtn.classList.add("copied"); setTimeout(() => { copyBtn.innerHTML = ` Copy`; copyBtn.classList.remove("copied"); }, 2000); }); });