Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Transformers.js Text Generator</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@xenova/[email protected]"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.typewriter { | |
border-right: 3px solid #4f46e5; | |
white-space: nowrap; | |
overflow: hidden; | |
animation: blink-caret 0.75s step-end infinite; | |
} | |
@keyframes blink-caret { | |
from, to { border-color: transparent } | |
50% { border-color: #4f46e5; } | |
} | |
.fade-in { | |
animation: fadeIn 0.5s ease-in; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
.scrollbar-hide::-webkit-scrollbar { | |
display: none; | |
} | |
.scrollbar-hide { | |
-ms-overflow-style: none; | |
scrollbar-width: none; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-50 min-h-screen"> | |
<div class="container mx-auto px-4 py-8 max-w-4xl"> | |
<header class="mb-10 text-center"> | |
<h1 class="text-4xl font-bold text-indigo-600 mb-2">Transformers.js</h1> | |
<h2 class="text-2xl font-semibold text-gray-700">GPT-2 Text Generator</h2> | |
<p class="text-gray-500 mt-2">Powered by the browser with no server required</p> | |
</header> | |
<div class="bg-white rounded-xl shadow-lg overflow-hidden mb-8"> | |
<div class="p-6"> | |
<div class="flex items-center justify-between mb-4"> | |
<h3 class="text-lg font-medium text-gray-900">Text Generation</h3> | |
<div class="flex items-center space-x-2"> | |
<span id="model-status" class="px-3 py-1 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800"> | |
Model: Loading... | |
</span> | |
<button id="info-btn" class="text-gray-400 hover:text-indigo-600 transition-colors"> | |
<i class="fas fa-info-circle"></i> | |
</button> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<label for="prompt" class="block text-sm font-medium text-gray-700 mb-2">Prompt</label> | |
<div class="relative"> | |
<textarea | |
id="prompt" | |
rows="4" | |
class="block w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500" | |
placeholder="Start typing your prompt here..."></textarea> | |
<div class="absolute bottom-3 right-3 flex space-x-2"> | |
<button id="clear-btn" class="p-1 text-gray-400 hover:text-indigo-600 transition-colors"> | |
<i class="fas fa-trash-alt"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> | |
<div> | |
<label for="max-length" class="block text-sm font-medium text-gray-700 mb-1">Max Length</label> | |
<input type="range" id="max-length" min="10" max="500" value="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
<div class="flex justify-between text-xs text-gray-500"> | |
<span>10</span> | |
<span id="max-length-value">50</span> | |
<span>500</span> | |
</div> | |
</div> | |
<div> | |
<label for="temperature" class="block text-sm font-medium text-gray-700 mb-1">Temperature</label> | |
<input type="range" id="temperature" min="0.1" max="1.5" step="0.1" value="0.7" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
<div class="flex justify-between text-xs text-gray-500"> | |
<span>0.1</span> | |
<span id="temperature-value">0.7</span> | |
<span>1.5</span> | |
</div> | |
</div> | |
</div> | |
<div class="flex justify-end space-x-3"> | |
<button id="stop-btn" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 bg-white hover:bg-gray-50 hidden"> | |
<i class="fas fa-stop mr-2"></i> Stop | |
</button> | |
<button id="generate-btn" class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors flex items-center"> | |
<i class="fas fa-bolt mr-2"></i> Generate | |
</button> | |
</div> | |
</div> | |
</div> | |
<div id="output-container" class="bg-white rounded-xl shadow-lg overflow-hidden hidden"> | |
<div class="p-6"> | |
<div class="flex items-center justify-between mb-4"> | |
<h3 class="text-lg font-medium text-gray-900">Generated Text</h3> | |
<button id="copy-btn" class="px-3 py-1 text-sm text-indigo-600 hover:text-indigo-800 transition-colors flex items-center"> | |
<i class="fas fa-copy mr-2"></i> Copy | |
</button> | |
</div> | |
<div id="output" class="bg-gray-50 p-4 rounded-lg min-h-32 max-h-96 overflow-y-auto scrollbar-hide"> | |
<p class="text-gray-500 italic">Generated text will appear here...</p> | |
</div> | |
<div class="mt-4 flex justify-between items-center text-sm text-gray-500"> | |
<div id="stats" class="flex space-x-4"> | |
<span id="time-taken"><i class="fas fa-stopwatch mr-1"></i> --</span> | |
<span id="tokens-generated"><i class="fas fa-key mr-1"></i> -- tokens</span> | |
</div> | |
<button id="regenerate-btn" class="text-indigo-600 hover:text-indigo-800 transition-colors flex items-center"> | |
<i class="fas fa-sync-alt mr-2"></i> Regenerate | |
</button> | |
</div> | |
</div> | |
</div> | |
<div id="info-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> | |
<div class="bg-white rounded-xl max-w-md w-full mx-4 p-6"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-lg font-medium text-gray-900">About This Demo</h3> | |
<button id="close-modal" class="text-gray-400 hover:text-gray-500"> | |
<i class="fas fa-times"></i> | |
</button> | |
</div> | |
<div class="space-y-3 text-gray-600"> | |
<p>This demo uses <span class="font-semibold">Transformers.js</span> to run a GPT-2 model directly in your browser with WebAssembly.</p> | |
<p>The model is quantized to reduce size and runs entirely client-side - no server processing required!</p> | |
<p><span class="font-semibold">Parameters:</span></p> | |
<ul class="list-disc pl-5 space-y-1"> | |
<li><span class="font-medium">Max Length:</span> Controls how many tokens to generate</li> | |
<li><span class="font-medium">Temperature:</span> Higher values make output more random</li> | |
</ul> | |
<p class="pt-2 text-sm text-indigo-600"><i class="fas fa-info-circle mr-2"></i> First generation may take longer as the model loads.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// DOM Elements | |
const promptInput = document.getElementById('prompt'); | |
const generateBtn = document.getElementById('generate-btn'); | |
const stopBtn = document.getElementById('stop-btn'); | |
const clearBtn = document.getElementById('clear-btn'); | |
const copyBtn = document.getElementById('copy-btn'); | |
const regenerateBtn = document.getElementById('regenerate-btn'); | |
const outputContainer = document.getElementById('output-container'); | |
const outputDiv = document.getElementById('output'); | |
const maxLengthSlider = document.getElementById('max-length'); | |
const maxLengthValue = document.getElementById('max-length-value'); | |
const temperatureSlider = document.getElementById('temperature'); | |
const temperatureValue = document.getElementById('temperature-value'); | |
const timeTaken = document.getElementById('time-taken'); | |
const tokensGenerated = document.getElementById('tokens-generated'); | |
const modelStatus = document.getElementById('model-status'); | |
const infoBtn = document.getElementById('info-btn'); | |
const infoModal = document.getElementById('info-modal'); | |
const closeModal = document.getElementById('close-modal'); | |
// App State | |
let pipeline = null; | |
let controller = null; | |
let isGenerating = false; | |
let modelLoaded = false; | |
// Initialize the model | |
async function initializeModel() { | |
try { | |
modelStatus.textContent = 'Model: Loading...'; | |
// Load the GPT-2 model (quantized version for better performance) | |
pipeline = await transformers.AutoModelForCausalLM.from_pretrained('Xenova/gpt2', { | |
quantized: true, | |
progress_callback: (progress) => { | |
const percent = Math.floor(progress * 100); | |
modelStatus.textContent = `Model: Loading ${percent}%`; | |
} | |
}); | |
modelStatus.textContent = 'Model: Ready'; | |
modelStatus.className = 'px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800'; | |
modelLoaded = true; | |
console.log('Model loaded successfully'); | |
} catch (error) { | |
console.error('Error loading model:', error); | |
modelStatus.textContent = 'Model: Failed to load'; | |
modelStatus.className = 'px-3 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800'; | |
} | |
} | |
// Generate text function | |
async function generateText() { | |
if (!modelLoaded) { | |
alert('Model is still loading. Please wait...'); | |
return; | |
} | |
const prompt = promptInput.value.trim(); | |
if (!prompt) { | |
alert('Please enter a prompt'); | |
return; | |
} | |
isGenerating = true; | |
generateBtn.disabled = true; | |
stopBtn.classList.remove('hidden'); | |
outputContainer.classList.remove('hidden'); | |
// Clear previous output | |
outputDiv.innerHTML = '<p class="typewriter">Generating...</p>'; | |
const maxLength = parseInt(maxLengthSlider.value); | |
const temperature = parseFloat(temperatureSlider.value); | |
// Start timing | |
const startTime = performance.now(); | |
try { | |
// Create a new AbortController for this generation | |
controller = new AbortController(); | |
// Generate text | |
const output = await pipeline(prompt, { | |
max_length: maxLength, | |
temperature: temperature, | |
do_sample: true, | |
signal: controller.signal, | |
callback_function: (text) => { | |
// Update output as it's being generated | |
outputDiv.innerHTML = `<p class="fade-in">${text}</p>`; | |
} | |
}); | |
// Calculate stats | |
const endTime = performance.now(); | |
const duration = ((endTime - startTime) / 1000).toFixed(2); | |
const tokens = output.length; | |
// Update stats | |
timeTaken.innerHTML = `<i class="fas fa-stopwatch mr-1"></i> ${duration}s`; | |
tokensGenerated.innerHTML = `<i class="fas fa-key mr-1"></i> ${tokens} tokens`; | |
// Final output | |
outputDiv.innerHTML = `<p class="fade-in">${output}</p>`; | |
} catch (error) { | |
if (error.name === 'AbortError') { | |
console.log('Generation stopped by user'); | |
outputDiv.innerHTML = '<p class="text-gray-500 italic">Generation stopped.</p>'; | |
} else { | |
console.error('Error during generation:', error); | |
outputDiv.innerHTML = `<p class="text-red-500">Error: ${error.message}</p>`; | |
} | |
} finally { | |
isGenerating = false; | |
generateBtn.disabled = false; | |
stopBtn.classList.add('hidden'); | |
controller = null; | |
} | |
} | |
// Stop generation | |
function stopGeneration() { | |
if (controller && isGenerating) { | |
controller.abort(); | |
isGenerating = false; | |
generateBtn.disabled = false; | |
stopBtn.classList.add('hidden'); | |
} | |
} | |
// Event Listeners | |
generateBtn.addEventListener('click', generateText); | |
stopBtn.addEventListener('click', stopGeneration); | |
clearBtn.addEventListener('click', () => { | |
promptInput.value = ''; | |
promptInput.focus(); | |
}); | |
copyBtn.addEventListener('click', () => { | |
const textToCopy = outputDiv.textContent; | |
navigator.clipboard.writeText(textToCopy).then(() => { | |
// Show feedback | |
const originalText = copyBtn.innerHTML; | |
copyBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Copied!'; | |
setTimeout(() => { | |
copyBtn.innerHTML = originalText; | |
}, 2000); | |
}); | |
}); | |
regenerateBtn.addEventListener('click', () => { | |
if (promptInput.value.trim()) { | |
generateText(); | |
} | |
}); | |
maxLengthSlider.addEventListener('input', () => { | |
maxLengthValue.textContent = maxLengthSlider.value; | |
}); | |
temperatureSlider.addEventListener('input', () => { | |
temperatureValue.textContent = temperatureSlider.value; | |
}); | |
infoBtn.addEventListener('click', () => { | |
infoModal.classList.remove('hidden'); | |
}); | |
closeModal.addEventListener('click', () => { | |
infoModal.classList.add('hidden'); | |
}); | |
infoModal.addEventListener('click', (e) => { | |
if (e.target === infoModal) { | |
infoModal.classList.add('hidden'); | |
} | |
}); | |
// Initialize the app | |
document.addEventListener('DOMContentLoaded', () => { | |
initializeModel(); | |
// Focus the prompt input on page load | |
promptInput.focus(); | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dauksza123/local-ai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |