Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<title>HTML Compressor for LLM</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism.min.css"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/plugins/toolbar/prism-toolbar.min.css"> | |
<style> | |
:root { | |
--primary-color: #007bff; | |
--secondary-color: #6c757d; | |
--success-color: #28a745; | |
--border-color: #dee2e6; | |
--background-color: #f8f9fa; | |
} | |
body { | |
font-family: system-ui, -apple-system, sans-serif; | |
line-height: 1.6; | |
margin: 0; | |
padding: 20px; | |
background: var(--background-color); | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
background: white; | |
padding: 30px; | |
border-radius: 8px; | |
box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
} | |
textarea { | |
width: 100%; | |
height: 200px; | |
padding: 12px; | |
border: 1px solid var(--border-color); | |
border-radius: 4px; | |
font-family: 'Monaco', 'Menlo', monospace; | |
font-size: 14px; | |
resize: vertical; | |
margin-bottom: 15px; | |
} | |
.options-container { | |
background: var(--background-color); | |
padding: 20px; | |
border-radius: 8px; | |
margin: 20px 0; | |
} | |
.option-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
gap: 15px; | |
} | |
.option-item { | |
display: flex; | |
align-items: center; | |
gap: 10px; | |
} | |
.button-group { | |
display: flex; | |
gap: 10px; | |
margin: 15px 0; | |
} | |
button { | |
background: var(--primary-color); | |
color: white; | |
padding: 8px 16px; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
transition: background 0.2s; | |
} | |
button:hover { | |
background: #0056b3; | |
} | |
.results-container { | |
margin-top: 30px; | |
} | |
.results-tabs { | |
display: flex; | |
gap: 10px; | |
margin-bottom: 15px; | |
} | |
.tab { | |
padding: 8px 16px; | |
cursor: pointer; | |
border: 1px solid var(--border-color); | |
border-radius: 4px; | |
transition: all 0.2s; | |
} | |
.tab.active { | |
background: var(--primary-color); | |
color: white; | |
} | |
.result-panel { | |
border: 1px solid var(--border-color); | |
border-radius: 4px; | |
overflow: hidden; | |
} | |
.result-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 10px; | |
background: var(--background-color); | |
border-bottom: 1px solid var(--border-color); | |
} | |
.result-content { | |
padding: 15px; | |
overflow: auto; | |
max-height: 500px; | |
} | |
.stats-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 15px; | |
margin: 20px 0; | |
} | |
.stat-item { | |
background: white; | |
padding: 15px; | |
border-radius: 4px; | |
border: 1px solid var(--border-color); | |
} | |
.stat-value { | |
font-size: 1.2em; | |
font-weight: bold; | |
color: var(--primary-color); | |
} | |
.copy-feedback { | |
position: fixed; | |
bottom: 20px; | |
right: 20px; | |
background: var(--success-color); | |
color: white; | |
padding: 10px 20px; | |
border-radius: 4px; | |
display: none; | |
} | |
.operation-status { | |
margin: 20px 0; | |
padding: 15px; | |
border: 1px solid var(--border-color); | |
border-radius: 4px; | |
} | |
.status-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 10px; | |
margin-top: 10px; | |
} | |
.status-item { | |
display: flex; | |
align-items: center; | |
gap: 8px; | |
padding: 8px; | |
border-radius: 4px; | |
background: var(--background-color); | |
} | |
.status-icon { | |
width: 20px; | |
height: 20px; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
font-size: 12px; | |
} | |
.status-success { | |
background: var(--success-color); | |
} | |
.status-error { | |
background: #dc3545; | |
} | |
.status-message { | |
font-size: 0.9em; | |
color: #666; | |
margin-top: 4px; | |
} | |
pre { | |
margin: 0; | |
border-radius: 4px; | |
} | |
code { | |
font-family: 'Monaco', 'Menlo', monospace; | |
font-size: 14px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>HTML Compressor for LLM</h1> | |
<p>Compress HTML content for optimal LLM processing while preserving essential structure.</p> | |
<form id="compressorForm"> | |
<textarea | |
name="html" | |
id="htmlInput" | |
placeholder="Paste your HTML here or upload a file..." | |
></textarea> | |
<div class="options-container"> | |
<h3>Compression Options</h3> | |
<div class="option-grid"> | |
<div class="option-item"> | |
<input type="checkbox" id="cleanHead" name="cleanHead" checked> | |
<label for="cleanHead">Clean head section</label> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="removeScripts" name="removeScripts" checked> | |
<label for="removeScripts">Remove scripts</label> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="removeStyles" name="removeStyles" checked> | |
<label for="removeStyles">Remove styles</label> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="handleRepeatingElements" name="handleRepeatingElements" checked> | |
<label for="handleRepeatingElements">Handle repeating elements</label> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="truncateText" name="truncateText" checked> | |
<label for="truncateText">Truncate text</label> | |
</div> | |
<div class="option-item"> | |
<label for="truncateLength">Max text length:</label> | |
<input type="number" id="truncateLength" name="truncateLength" value="100" min="10" max="1000"> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="minifyHtml" name="minifyHtml" checked> | |
<label for="minifyHtml">Minify HTML</label> | |
</div> | |
<div class="option-item"> | |
<input type="checkbox" id="removeMedia" name="removeMedia" checked> | |
<label for="removeMedia">Remove media</label> | |
</div> | |
</div> | |
</div> | |
<div class="button-group"> | |
<input type="file" accept=".html,.htm" id="fileInput"> | |
<button type="submit">Process HTML</button> | |
</div> | |
</form> | |
<div id="operationStatus" class="operation-status" style="display: none;"> | |
<h3>Operation Status</h3> | |
<div class="status-grid"></div> | |
</div> | |
<div id="stats" class="stats-grid" style="display: none;"></div> | |
<div class="results-container" style="display: none;"> | |
<div class="results-tabs"> | |
<div class="tab active" data-view="html">Compressed HTML</div> | |
<div class="tab" data-view="json">JSON Structure</div> | |
</div> | |
<div class="result-panel" id="htmlView"> | |
<div class="result-header"> | |
<h3>HTML Output</h3> | |
<div class="button-group"> | |
<button onclick="copyResult('html')">Copy</button> | |
<button onclick="downloadResult('html')">Download</button> | |
</div> | |
</div> | |
<div class="result-content"> | |
<pre><code class="language-html" id="htmlOutput"></code></pre> | |
</div> | |
</div> | |
<div class="result-panel" id="jsonView" style="display: none;"> | |
<div class="result-header"> | |
<h3>JSON Structure</h3> | |
<div class="button-group"> | |
<button onclick="copyResult('json')">Copy</button> | |
<button onclick="downloadResult('json')">Download</button> | |
</div> | |
</div> | |
<div class="result-content"> | |
<pre><code class="language-json" id="jsonOutput"></code></pre> | |
</div> | |
</div> | |
</div> | |
<div class="copy-feedback">Copied to clipboard!</div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-markup.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-json.min.js"></script> | |
<script> | |
const form = document.getElementById('compressorForm'); | |
const fileInput = document.getElementById('fileInput'); | |
const htmlInput = document.getElementById('htmlInput'); | |
const resultsContainer = document.querySelector('.results-container'); | |
const statsContainer = document.getElementById('stats'); | |
const copyFeedback = document.querySelector('.copy-feedback'); | |
// Tab switching | |
document.querySelectorAll('.tab').forEach(tab => { | |
tab.addEventListener('click', () => { | |
// Update tabs | |
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
tab.classList.add('active'); | |
// Update views | |
const view = tab.dataset.view; | |
document.getElementById('htmlView').style.display = view === 'html' ? 'block' : 'none'; | |
document.getElementById('jsonView').style.display = view === 'json' ? 'block' : 'none'; | |
}); | |
}); | |
// File input handler | |
fileInput.addEventListener('change', (e) => { | |
const file = e.target.files[0]; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = (e) => htmlInput.value = e.target.result; | |
reader.readAsText(file); | |
} | |
}); | |
// Form submission | |
form.addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
const formData = new FormData(form); | |
// Add checkbox states | |
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { | |
formData.set(checkbox.name, checkbox.checked); | |
}); | |
try { | |
const response = await fetch('/process', { | |
method: 'POST', | |
body: formData, | |
}); | |
const data = await response.json(); | |
if (data.error) { | |
alert(data.error); | |
return; | |
} | |
// Display operation status | |
const statusContainer = document.querySelector('#operationStatus'); | |
const statusGrid = statusContainer.querySelector('.status-grid'); | |
statusContainer.style.display = 'block'; | |
statusGrid.innerHTML = Object.entries(data.operationStatus) | |
.map(([operation, status]) => ` | |
<div class="status-item"> | |
<div class="status-icon ${status.success ? 'status-success' : 'status-error'}"> | |
${status.success ? '✓' : '✗'} | |
</div> | |
<div> | |
<div>${formatLabel(operation)}</div> | |
${status.error ? `<div class="status-message">Error: ${status.error}</div>` : ''} | |
</div> | |
</div> | |
`).join(''); | |
// Display stats | |
statsContainer.style.display = 'grid'; | |
statsContainer.innerHTML = Object.entries(data.stats) | |
.map(([key, value]) => ` | |
<div class="stat-item"> | |
<div class="stat-label">${formatLabel(key)}</div> | |
<div class="stat-value">${value}</div> | |
</div> | |
`).join(''); | |
// Show results container | |
resultsContainer.style.display = 'block'; | |
// Update outputs with syntax highlighting | |
document.getElementById('htmlOutput').textContent = data.result.html; | |
document.getElementById('jsonOutput').textContent = data.result.json; | |
// Trigger Prism highlighting | |
Prism.highlightAll(); | |
} catch (err) { | |
alert('Error processing HTML: ' + err.message); | |
} | |
}); | |
// Utility functions | |
function formatLabel(key) { | |
return key | |
.replace(/([A-Z])/g, ' $1') | |
.replace(/([a-z])([A-Z])/g, '$1 $2') | |
.toLowerCase() | |
.replace(/^./, str => str.toUpperCase()) | |
.replace('Html', 'HTML'); | |
} | |
async function copyResult(type) { | |
const content = document.getElementById(`${type}Output`).textContent; | |
try { | |
await navigator.clipboard.writeText(content); | |
showCopyFeedback(); | |
} catch (err) { | |
alert('Failed to copy to clipboard'); | |
} | |
} | |
function downloadResult(type) { | |
const content = document.getElementById(`${type}Output`).textContent; | |
const blob = new Blob([content], { type: 'text/plain' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = `compressed.${type}`; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
} | |
function showCopyFeedback() { | |
copyFeedback.style.display = 'block'; | |
setTimeout(() => { | |
copyFeedback.style.display = 'none'; | |
}, 2000); | |
} | |
</script> | |
</body> | |
</html> |