| <!DOCTYPE html> |
| <html lang="id"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Prompt Generator GPT-2</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
|
|
| <script type="module"> |
| import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2'; |
| window.pipeline = pipeline; |
| env.allowLocalModels = false; |
| </script> |
| <style> |
| |
| *, *::before, *::after { |
| -webkit-tap-highlight-color: transparent; |
| } |
| *:focus, *:active { |
| outline: none !important; |
| box-shadow: none !important; |
| } |
| |
| body { |
| background: linear-gradient(135deg, #f0f4f9 0%, #e1e9f5 100%); |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |
| } |
| |
| .btn-silver { |
| background: linear-gradient(to bottom, #ffffff 0%, #dbe1e8 100%); |
| border: 1px solid #b0bac5; |
| color: #1e3a8a; |
| transition: all 0.15s ease-in-out; |
| } |
| .btn-silver:hover:not(:disabled) { |
| background: linear-gradient(to bottom, #ffffff 0%, #cbd5e1 100%); |
| border-color: #94a3b8; |
| } |
| .btn-silver:active:not(:disabled) { |
| background: linear-gradient(to bottom, #dbe1e8 0%, #ffffff 100%); |
| } |
| .btn-silver:disabled { |
| opacity: 0.6; |
| cursor: not-allowed; |
| background: linear-gradient(to bottom, #f1f5f9 0%, #e2e8f0 100%); |
| } |
| |
| |
| #sapFocusOverlay { |
| position: fixed; |
| pointer-events: none; |
| z-index: 9999; |
| transition: top 0.22s cubic-bezier(0.25, 1, 0.5, 1), |
| left 0.22s cubic-bezier(0.25, 1, 0.5, 1), |
| width 0.22s cubic-bezier(0.25, 1, 0.5, 1), |
| height 0.22s cubic-bezier(0.25, 1, 0.5, 1), |
| opacity 0.15s ease; |
| opacity: 0; |
| } |
| |
| .sap-corner { |
| position: absolute; |
| width: 6px; |
| height: 6px; |
| border-color: #cc2222; |
| border-style: solid; |
| transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1); |
| } |
| |
| .sap-tl { top: -1px; left: -2px; border-width: 2px 0 0 2px; } |
| .sap-tr { top: -1px; right: -2px; border-width: 2px 2px 0 0; } |
| .sap-bl { bottom: -1px; left: -2px; border-width: 0 0 2px 2px; } |
| .sap-br { bottom: -1px; right: -2px; border-width: 0 2px 2px 0; } |
| |
| |
| #sapFocusOverlay.active .sap-tl { transform: translate(0, 0); } |
| #sapFocusOverlay.active .sap-tr { transform: translate(0, 0); } |
| #sapFocusOverlay.active .sap-bl { transform: translate(0, 0); } |
| #sapFocusOverlay.active .sap-br { transform: translate(0, 0); } |
| </style> |
| </head> |
| <body class="min-h-screen flex flex-col items-center justify-start p-4 py-8 text-slate-800"> |
|
|
| <div class="w-full max-w-4xl space-y-6"> |
| |
|
|
| <div class="text-center space-y-1.5"> |
| <h1 class="text-2xl sm:text-3xl font-extrabold text-blue-600 tracking-tight">Prompt Generator</h1> |
| <p class="text-xs sm:text-sm text-slate-500 font-medium"> |
| Run model <code class="bg-blue-50 px-2 py-0.5 rounded border border-blue-100 text-blue-600 font-mono text-xs">GPT-2</code> with Transformers.js |
| </p> |
| </div> |
|
|
|
|
| <div id="statusBox" tabindex="0" class="interactive-element bg-white/80 border border-slate-200 p-3.5 rounded-2xl text-xs flex items-center space-x-3 shadow-sm transition"> |
|
|
| <div id="statusDot" class="relative flex h-3 w-3 ml-1"> |
| <span id="dotPing" class="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span> |
| <span id="dotSolid" class="relative inline-flex rounded-full h-3 w-3 bg-amber-500"></span> |
| </div> |
| <span id="statusText" class="text-slate-600 font-semibold">Prepare AI engine...</span> |
| </div> |
|
|
|
|
| <div class="space-y-1.5"> |
| <label class="block text-xs font-semibold text-slate-600 uppercase tracking-wider"> |
| Initial Prompts |
| </label> |
| <div class="relative"> |
| <textarea |
| id="initialPrompt" |
| rows="4" |
| placeholder="Example: A futuristic sports car driving through a cyberpunk city at night..." |
| class="interactive-element w-full bg-white border border-slate-300 rounded-2xl p-4 pb-6 text-xs sm:text-sm text-slate-800 shadow-sm transition resize-y block" |
| ></textarea> |
| |
|
|
| <div class="absolute bottom-2.5 right-4 pointer-events-none flex items-center gap-1 text-[10px] text-slate-400 font-mono select-none"> |
| <span>Resizable</span> |
| <svg class="w-2.5 h-2.5 text-slate-400" fill="currentColor" viewBox="0 0 16 16"> |
| <path d="M14 14H12V12H14V14ZM14 10H12V8H14V10ZM10 14H8V12H10V14Z"/> |
| </svg> |
| </div> |
| </div> |
| <div class="text-[11px] text-slate-400 px-1"> |
| <span>Enter for next lines</span> |
| </div> |
| </div> |
|
|
|
|
| <div class="space-y-3"> |
| <button |
| id="generateBtn" |
| onclick="generatePrompt()" |
| disabled |
| class="btn-silver interactive-element w-full font-bold py-3.5 rounded-2xl text-xs sm:text-sm shadow-sm transition-all duration-200 flex items-center justify-center space-x-2" |
| > |
| <span id="btnText">Loading Model...</span> |
| </button> |
|
|
|
|
| <div id="loadingBarContainer" class="hidden w-full space-y-1.5"> |
| <div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden border border-slate-300"> |
| <div id="progressBar" class="bg-blue-600 h-full w-0 transition-all duration-300 ease-out"></div> |
| </div> |
| <div class="flex justify-between text-[10px] text-slate-500 font-medium"> |
| <span>Processing...</span> |
| <span id="progressPercent">0%</span> |
| </div> |
| </div> |
| </div> |
|
|
|
|
| <div id="errorBox" class="hidden text-red-600 bg-red-50 border border-red-200 p-3.5 rounded-2xl text-xs sm:text-sm"></div> |
|
|
|
|
| <div class="space-y-1.5"> |
| <label class="block text-xs font-semibold text-slate-600 uppercase tracking-wider"> |
| Outputs |
| </label> |
| <div class="relative"> |
| <div |
| id="outputPrompt" |
| tabindex="0" |
| class="interactive-element w-full min-h-[120px] bg-white border border-slate-300 rounded-2xl p-4 pb-6 text-sm text-slate-800 font-mono leading-relaxed flex flex-wrap content-start items-center gap-1.5 overflow-y-auto resize-y shadow-sm" |
| > |
| <span class="text-slate-400 text-xs italic font-sans select-none">Generated output will appear here...</span> |
| </div> |
| |
| <button |
| onclick="copyToClipboard()" |
| class="btn-silver interactive-element absolute top-3 right-3 text-xs px-3.5 py-1.5 rounded-xl font-semibold shadow-sm z-20" |
| > |
| Copy |
| </button> |
|
|
| <div class="absolute bottom-2.5 right-4 pointer-events-none flex items-center gap-1 text-[10px] text-slate-400 font-mono select-none z-10"> |
| <span>Resizable</span> |
| <svg class="w-2.5 h-2.5 text-slate-400" fill="currentColor" viewBox="0 0 16 16"> |
| <path d="M14 14H12V12H14V14ZM14 10H12V8H14V10ZM10 14H8V12H10V14Z"/> |
| </svg> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
|
|
| <div id="sapFocusOverlay"> |
| <div class="sap-corner sap-tl"></div> |
| <div class="sap-corner sap-tr"></div> |
| <div class="sap-corner sap-bl"></div> |
| <div class="sap-corner sap-br"></div> |
| </div> |
|
|
| <script> |
| |
| const overlay = document.getElementById('sapFocusOverlay'); |
| |
| function attachFocusAnimator(target) { |
| if (!target || target === document.body) { |
| overlay.style.opacity = '0'; |
| return; |
| } |
| |
| const rect = target.getBoundingClientRect(); |
| const padding = 3; |
| |
| overlay.style.top = `${rect.top - padding}px`; |
| overlay.style.left = `${rect.left - padding}px`; |
| overlay.style.width = `${rect.width + (padding * 2)}px`; |
| overlay.style.height = `${rect.height + (padding * 2)}px`; |
| overlay.style.opacity = '1'; |
| overlay.classList.add('active'); |
| } |
| |
| |
| document.addEventListener('focusin', (e) => { |
| const target = e.target.closest('textarea, input, button, select, [tabindex], .interactive-element'); |
| if (target) { |
| attachFocusAnimator(target); |
| } |
| }); |
| |
| document.addEventListener('click', (e) => { |
| const target = e.target.closest('textarea, input, button, select, [tabindex], .interactive-element'); |
| if (target) { |
| attachFocusAnimator(target); |
| } else { |
| overlay.style.opacity = '0'; |
| } |
| }); |
| |
| window.addEventListener('resize', () => { |
| if (document.activeElement) attachFocusAnimator(document.activeElement); |
| }); |
| window.addEventListener('scroll', () => { |
| if (document.activeElement) attachFocusAnimator(document.activeElement); |
| }, true); |
| |
| |
| |
| let generator = null; |
| |
| const tokenColors = [ |
| 'bg-amber-100 text-amber-900 border border-amber-200', |
| 'bg-emerald-100 text-emerald-900 border border-emerald-200', |
| 'bg-blue-100 text-blue-900 border border-blue-200', |
| 'bg-rose-100 text-rose-900 border border-rose-200', |
| 'bg-purple-100 text-purple-900 border border-purple-200', |
| 'bg-orange-100 text-orange-900 border border-orange-200', |
| 'bg-cyan-100 text-cyan-900 border border-cyan-200' |
| ]; |
| |
| window.addEventListener('DOMContentLoaded', async () => { |
| const statusText = document.getElementById('statusText'); |
| const dotPing = document.getElementById('dotPing'); |
| const dotSolid = document.getElementById('dotSolid'); |
| const btn = document.getElementById('generateBtn'); |
| const btnText = document.getElementById('btnText'); |
| |
| try { |
| while (!window.pipeline) { |
| await new Promise(r => setTimeout(r, 100)); |
| } |
| |
| statusText.textContent = 'Importing model GPT-2...'; |
| |
| generator = await window.pipeline('text-generation', 'Xenova/gpt2'); |
| |
| dotPing.className = "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"; |
| dotSolid.className = "relative inline-flex rounded-full h-3 w-3 bg-emerald-500"; |
| |
| statusText.textContent = 'Model Ready'; |
| btnText.textContent = 'Generate'; |
| btn.disabled = false; |
| } catch (err) { |
| dotPing.className = "hidden"; |
| dotSolid.className = "relative inline-flex rounded-full h-3 w-3 bg-red-500"; |
| statusText.textContent = 'Failed to load model'; |
| console.error(err); |
| } |
| }); |
| |
| async function generatePrompt() { |
| const rawInput = document.getElementById('initialPrompt').value.trim(); |
| const initialPrompt = rawInput.replace(/[\r\n]+/g, " "); |
| |
| const btn = document.getElementById('generateBtn'); |
| const btnText = document.getElementById('btnText'); |
| const outputContainer = document.getElementById('outputPrompt'); |
| const errorBox = document.getElementById('errorBox'); |
| |
| const loadingBarContainer = document.getElementById('loadingBarContainer'); |
| const progressBar = document.getElementById('progressBar'); |
| const progressPercent = document.getElementById('progressPercent'); |
| |
| errorBox.classList.add('hidden'); |
| |
| if (!initialPrompt) { |
| showError('Please make sure you type required entries.'); |
| return; |
| } |
| |
| btn.disabled = true; |
| btnText.textContent = 'Generating...'; |
| outputContainer.innerHTML = ''; |
| |
| loadingBarContainer.classList.remove('hidden'); |
| progressBar.style.width = '10%'; |
| progressPercent.textContent = '10%'; |
| |
| await new Promise(resolve => setTimeout(resolve, 50)); |
| |
| let progress = 10; |
| const progressInterval = setInterval(() => { |
| if (progress < 90) { |
| progress += Math.floor(Math.random() * 15) + 5; |
| if (progress > 90) progress = 90; |
| progressBar.style.width = `${progress}%`; |
| progressPercent.textContent = `${progress}%`; |
| } |
| }, 100); |
| |
| try { |
| const formattedInput = `${initialPrompt}, dynamic lighting, highly detailed, 8k resolution, masterpiece,`; |
| |
| const output = await generator(formattedInput, { |
| max_new_tokens: 40, |
| temperature: 0.7, |
| do_sample: true, |
| top_k: 40 |
| }); |
| |
| clearInterval(progressInterval); |
| progressBar.style.width = '100%'; |
| progressPercent.textContent = '100%'; |
| |
| if (output && output[0] && output[0].generated_text) { |
| let cleanText = output[0].generated_text.replace(/[\r\n]+/g, " "); |
| const tokens = cleanText.match(/[\w']+|[^\w\s]/g) || [cleanText]; |
| |
| outputContainer.innerHTML = ''; |
| |
| tokens.forEach((token, index) => { |
| const colorClass = tokenColors[index % tokenColors.length]; |
| const span = document.createElement('span'); |
| span.className = `${colorClass} px-1.5 py-0.5 rounded-md font-mono text-xs inline-block`; |
| span.textContent = token; |
| outputContainer.appendChild(span); |
| }); |
| } |
| } catch (err) { |
| clearInterval(progressInterval); |
| showError('An error occurred during generation.'); |
| } finally { |
| setTimeout(() => { |
| loadingBarContainer.classList.add('hidden'); |
| progressBar.style.width = '0%'; |
| btn.disabled = false; |
| btnText.textContent = 'Generate'; |
| }, 500); |
| } |
| } |
| |
| function showError(msg) { |
| const errorBox = document.getElementById('errorBox'); |
| errorBox.textContent = msg; |
| errorBox.classList.remove('hidden'); |
| } |
| |
| function copyToClipboard() { |
| const outputContainer = document.getElementById('outputPrompt'); |
| const textToCopy = Array.from(outputContainer.children) |
| .map(el => el.textContent) |
| .join(' '); |
| |
| if (!textToCopy.trim()) return; |
| |
| navigator.clipboard.writeText(textToCopy); |
| } |
| </script> |
| </body> |
| </html> |
|
|