Spaces:
Sleeping
Sleeping
| /* | |
| 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 = '<span class="placeholder-text">Generatingβ¦</span>'; | |
| 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 = '<span class="placeholder-text">Generated text will appear hereβ¦</span>'; | |
| } 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 = ` | |
| <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" | |
| stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="9" y="9" width="13" height="13" rx="2"/> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/> | |
| </svg> | |
| Copy`; | |
| copyBtn.classList.remove("copied"); | |
| }, 2000); | |
| }); | |
| }); |