Moshe Ofer
Initial commit for Hugging Face Space
3df6a65
raw
history blame
17.8 kB
<!DOCTYPE html>
<html>
<head>
<title>Beam Search Generation</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #4F46E5;
--secondary-color: #818CF8;
--background-color: #F3F4F6;
--card-background: #FFFFFF;
--text-primary: #111827;
--text-secondary: #4B5563;
--accent-color: #3730A3;
--success-color: #059669;
--border-radius: 12px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
background-color: var(--background-color);
color: var(--text-primary);
line-height: 1.5;
min-height: 100vh;
}
.header {
background: var(--card-background);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
margin-bottom: 1rem;
}
h1 {
font-size: 2rem;
font-weight: 700;
color: var(--accent-color);
text-align: center;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
padding: 1.5rem;
margin: 0;
}
.input-section {
background: var(--card-background);
padding: 1.5rem 2rem;
border-bottom: 1px solid #E5E7EB;
}
textarea {
width: 100%;
padding: 1rem;
border: 2px solid #E5E7EB;
border-radius: var(--border-radius);
font-size: 1rem;
transition: border-color 0.3s ease;
resize: vertical;
margin-bottom: 1rem;
}
textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1rem;
}
.input-group {
display: flex;
flex-direction: column;
}
label {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-secondary);
}
input[type="number"] {
padding: 0.75rem;
border: 2px solid #E5E7EB;
border-radius: var(--border-radius);
font-size: 1rem;
transition: all 0.3s ease;
}
input[type="number"]:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.slider-container {
margin: 1rem 0;
padding: 1rem;
background: var(--background-color);
border-radius: var(--border-radius);
}
.slider-group {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 0.5rem;
}
input[type="range"] {
flex: 1;
height: 8px;
-webkit-appearance: none;
background: #E5E7EB;
border-radius: 4px;
outline: none;
transition: all 0.3s ease;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--primary-color);
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: var(--accent-color);
transform: scale(1.1);
}
.slider-value {
min-width: 100px;
padding: 0.5rem 1rem;
background: var(--primary-color);
color: white;
border-radius: var(--border-radius);
text-align: center;
font-weight: 600;
font-size: 0.9rem;
}
#generate-btn {
background-color: var(--primary-color);
color: white;
border: none;
padding: 1rem 2rem;
border-radius: var(--border-radius);
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
width: 100%;
margin-top: 1rem;
}
#generate-btn:hover {
background-color: var(--accent-color);
transform: translateY(-1px);
}
#generate-btn:disabled {
background-color: #D1D5DB;
cursor: not-allowed;
transform: none;
}
.loading {
display: none;
text-align: center;
color: var(--text-secondary);
font-weight: 600;
padding: 0.5rem;
}
.container {
padding: 0 2rem;
}
.split-container {
display: flex;
gap: 2rem;
padding-bottom: 2rem;
}
.left-panel, .right-panel {
flex: 1;
background: var(--card-background);
border-radius: var(--border-radius);
padding: 1.5rem;
min-height: 300px;
}
.panel-title {
font-size: 1.25rem;
color: var(--accent-color);
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--secondary-color);
}
.beam-container {
background: var(--background-color);
margin-bottom: 1rem;
padding: 1.5rem;
border-radius: var(--border-radius);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease;
}
.beam-container:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.beam-container h3, .beam-container h4 {
color: var(--accent-color);
margin-bottom: 1rem;
font-weight: 600;
}
.beam-text {
white-space: pre-wrap;
font-family: 'Cascadia Code', 'Source Code Pro', monospace;
line-height: 1.6;
color: var(--text-secondary);
background: var(--card-background);
padding: 1rem;
border-radius: calc(var(--border-radius) - 4px);
}
@media (max-width: 768px) {
.container {
padding: 0 1rem;
}
.split-container {
flex-direction: column;
gap: 1rem;
}
.controls {
grid-template-columns: 1fr;
}
.header {
margin-bottom: 1rem;
}
}
.footer {
background: var(--card-background);
padding: 2rem;
margin-top: 2rem;
box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.05);
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
.project-info h3 {
color: var(--accent-color);
margin-bottom: 1rem;
font-size: 1.25rem;
}
.project-info p {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 1rem;
}
.credit {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
}
.credit p {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.credit a {
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
transition: color 0.3s ease;
}
.credit a:hover {
color: var(--accent-color);
}
.social-links {
display: flex;
gap: 1rem;
}
.social-links a {
color: var(--text-secondary);
font-size: 1.5rem;
transition: all 0.3s ease;
}
.social-links a:hover {
color: var(--primary-color);
transform: translateY(-2px);
}
@media (max-width: 768px) {
.footer-content {
grid-template-columns: 1fr;
gap: 1rem;
}
.credit {
align-items: flex-start;
}
}
</style>
</head>
<body>
<div class="header">
<h1>Beam Search Generator</h1>
<div class="input-section">
<textarea id="prompt" rows="4" placeholder="Enter your prompt here..."></textarea>
<div class="controls">
<div class="input-group">
<label for="num_beams">Number of beams</label>
<input type="number" id="num_beams" value="5" min="2" max="10">
</div>
<div class="input-group">
<label for="max_tokens">Max tokens</label>
<input type="number" id="max_tokens" value="512" min="1">
</div>
</div>
<div class="slider-container">
<label for="sleep_time">Generation Speed</label>
<div class="slider-group">
<i class="fas fa-bolt" title="Fast"></i>
<input type="range" id="sleep_time" min="0" max="500" value="0" step="10">
<i class="fas fa-hourglass" title="Slow"></i>
<div class="slider-value" id="sleep_value">0ms delay</div>
</div>
</div>
<button id="generate-btn" onclick="generate()">
<i class="fas fa-wand-magic-sparkles"></i>
Generate
</button>
<div id="loading" class="loading">
<i class="fas fa-spinner"></i>
Generating amazing content...
</div>
</div>
</div>
<div class="container">
<div class="split-container">
<div class="left-panel">
<h2 class="panel-title">Active Beams</h2>
<div id="beams"></div>
</div>
<div class="right-panel">
<h2 class="panel-title">Completed Beams</h2>
<div id="completed-list"></div>
</div>
</div>
</div>
<script>
// Replace the socket initialization with:
let socket = io({
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
path: '/socket.io/', // Explicitly set the path
upgrade: false // Disable transport upgrades
});
let beams = {};
let completedBeams = [];
let isGenerating = false;
// Add slider value update
const sleepSlider = document.getElementById('sleep_time');
const sleepValue = document.getElementById('sleep_value');
sleepSlider.addEventListener('input', function() {
const value = parseInt(this.value);
sleepValue.textContent = value === 0 ? 'No delay' : `${value}ms delay`;
});
function setupSocketListeners() {
socket.on('beam_update', function(data) {
console.log('Received beam update:', data); // Add logging
const { beam_idx, text } = data;
if (!beams[beam_idx]) {
createBeamContainer(beam_idx);
}
const beamElement = document.getElementById(`beam-${beam_idx}`);
if (beamElement) {
beamElement.textContent = text;
}
});
socket.on('beam_finished', function(data) {
completedBeams.push(data.text);
updateCompletedBeams();
});
socket.on('generation_started', function() {
isGenerating = true;
document.getElementById('generate-btn').disabled = true;
document.getElementById('loading').style.display = 'block';
document.getElementById('generate-btn').innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...';
});
socket.on('generation_completed', function() {
isGenerating = false;
document.getElementById('generate-btn').disabled = false;
document.getElementById('loading').style.display = 'none';
document.getElementById('generate-btn').innerHTML = '<i class="fas fa-wand-magic-sparkles"></i> Generate';
});
socket.on('generation_error', function(data) {
alert('Error during generation: ' + data.error);
isGenerating = false;
document.getElementById('generate-btn').disabled = false;
document.getElementById('loading').style.display = 'none';
document.getElementById('generate-btn').innerHTML = '<i class="fas fa-wand-magic-sparkles"></i> Generate';
});
socket.on('connect_error', function(error) {
console.error('Connection error:', error);
resetConnection();
});
}
function createBeamContainer(beamIdx) {
const container = document.createElement('div');
container.className = 'beam-container';
container.innerHTML = `
<h3>Beam ${beamIdx + 1}</h3>
<div id="beam-${beamIdx}" class="beam-text"></div>
`;
document.getElementById('beams').appendChild(container);
beams[beamIdx] = container;
}
function updateCompletedBeams() {
const completedList = document.getElementById('completed-list');
completedList.innerHTML = completedBeams.map((text, idx) => `
<div class="beam-container">
<h4>Completed Beam ${idx + 1}</h4>
<div class="beam-text">${text}</div>
</div>
`).join('');
}
function resetConnection() {
if (socket) {
socket.removeAllListeners();
socket.close();
}
socket = io({
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
setupSocketListeners();
}
function generate() {
if (isGenerating) return;
console.log('Starting generation...'); // Add logging
// Clear previous state
document.getElementById('beams').innerHTML = '';
document.getElementById('completed-list').innerHTML = '';
beams = {};
completedBeams = [];
// Reset connection before each generation
resetConnection();
const prompt = document.getElementById('prompt').value;
const numBeams = parseInt(document.getElementById('num_beams').value);
const maxTokens = parseInt(document.getElementById('max_tokens').value);
const sleepTime = parseInt(document.getElementById('sleep_time').value);
console.log('Emitting generate event with params:', {
prompt, numBeams, maxTokens, sleepTime
});
socket.emit('generate', {
prompt: prompt,
num_beams: numBeams,
max_tokens: maxTokens,
sleep_time: sleepTime
});
}
setupSocketListeners();
</script>
<footer class="footer">
<div class="footer-content">
<div class="project-info">
<h3>About This Project</h3>
<p>This website demonstrates the MultiBeamTextStreamer feature proposed in a pull request to the Hugging Face Transformers library. The MultiBeamTextStreamer enables real-time visualization of beam search generation, providing insights into how language models explore different text completion possibilities.</p>
<p>The implementation showcases how beam search works by displaying multiple candidate sequences simultaneously, making it a valuable educational tool for understanding text generation algorithms.</p>
</div>
<div class="credit">
<p>Created by <a href="https://github.com/mosheofer1" target="_blank" rel="noopener noreferrer">Moshe Ofer</a></p>
<div class="social-links">
<a href="https://github.com/mosheofer1" target="_blank" rel="noopener noreferrer" title="GitHub">
<i class="fab fa-github"></i>
</a>
<a href="https://www.linkedin.com/in/moshe-ofer/" target="_blank" rel="noopener noreferrer" title="linkedin">
<i class="fas fa-rocket"></i>
</a>
</div>
</div>
</div>
</footer>
</body>
</html>