Summarizer
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Text Summarizer - Zupr.ai</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
<style>
body {
font-family: 'Roboto', sans-serif;
max-width: 900px;
margin: 20px auto;
padding: 20px;
background: linear-gradient(135deg, #1e3c72, #2a5298);
color: #fff;
}
h1 {
text-align: center;
color: #00ddeb;
text-shadow: 0 0 10px rgba(0,221,235,0.5);
}
.container {
background: rgba(255,255,255,0.95);
padding: 20px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
textarea {
width: 100%;
height: 200px;
padding: 12px;
border: 1px solid #00ddeb;
border-radius: 6px;
resize: vertical;
font-size: 16px;
color: #333;
}
button {
background: #00ddeb;
color: #1e3c72;
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
margin: 10px 5px;
transition: transform 0.2s, background 0.2s;
position: relative;
}
button:hover {
transform: scale(1.05);
background: #00b8c4;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
.tooltip {
position: relative;
}
.tooltip:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: #fff;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 10;
}
#summary-output, #history, #key-phrases {
margin-top: 20px;
padding: 15px;
background: #f5faff;
border-radius: 6px;
color: #333;
animation: fadeIn 0.5s ease-in;
}
#original-text {
margin-top: 20px;
padding: 15px;
background: #e6f0fa;
border-radius: 6px;
color: #333;
}
.highlight {
background: #ffeb3b;
padding: 2px 4px;
}
#stats {
font-size: 14px;
color: #2a5298;
margin-top: 10px;
}
.slider-container, .select-container {
margin: 10px 0;
display: flex;
align-items: center;
gap: 10px;
}
label {
font-size: 14px;
color: #333;
margin-right: 10px;
}
select, input[type="range"] {
padding: 5px;
border: 1px solid #00ddeb;
border-radius: 4px;
}
.loading {
display: none;
text-align: center;
margin: 10px 0;
color: #2a5298;
}
#history-list li {
cursor: pointer;
padding: 5px 0;
}
#history-list li:hover {
background: #e6f0fa;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@media (max-width: 600px) {
body { padding: 10px; }
.container { padding: 15px; }
button { padding: 8px 12px; font-size: 14px; }
.slider-container, .select-container { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<h1>Zupr.ai Text Summarizer</h1>
<div class="container">
<textarea id="input-text" placeholder="Paste or type your text here..." aria-label="Input text for summarization"></textarea>
<div class="slider-container">
<label for="summary-length">Summary Length: <span id="length-value">5</span> sentences</label>
<input type="range" id="summary-length" min="1" max="10" value="5" aria-label="Adjust summary length">
</div>
<div class="slider-container">
<label for="word-limit">Word Limit: <span id="word-limit-value">100</span> words</label>
<input type="range" id="word-limit" min="50" max="300" value="100" step="10" aria-label="Adjust word limit">
</div>
<div class="select-container">
<label for="tone">Tone:</label>
<select id="tone" aria-label="Select summary tone">
<option value="neutral">Neutral</option>
<option value="formal">Formal</option>
<option value="informal">Informal</option>
</select>
<label for="style">Style:</label>
<select id="style" aria-label="Select summary style">
<option value="bullets">Bullet Points</option>
<option value="paragraph">Paragraph</option>
</select>
</div>
<div class="loading" id="loading"><i class="fas fa-spinner fa-spin"></i> Summarizing...</div>
<button onclick="summarizeText()" class="tooltip" data-tooltip="Generate summary" id="summarize-btn"><i class="fas fa-compress-alt"></i> Summarize</button>
<button onclick="copySummary()" class="tooltip" data-tooltip="Copy summary to clipboard"><i class="fas fa-copy"></i> Copy</button>
<button onclick="downloadSummary()" class="tooltip" data-tooltip="Download summary as text file"><i class="fas fa-download"></i> Download</button>
<button onclick="clearText()" class="tooltip" data-tooltip="Clear all fields"><i class="fas fa-trash"></i> Clear</button>
<div id="summary-output" role="region" aria-live="polite">Your summary will appear here...</div>
<div id="key-phrases" role="region" aria-live="polite">Key phrases will appear here...</div>
<div id="original-text" role="region" aria-live="polite">Highlighted original text will appear here...</div>
<div id="stats"></div>
<div id="history">
<h3>Recent Summaries</h3>
<ul id="history-list"></ul>
</div>
</div>
<script>
async function summarizeText() {
const inputText = document.getElementById('input-text').value.trim();
const summaryLength = parseInt(document.getElementById('summary-length').value);
const wordLimit = parseInt(document.getElementById('word-limit').value);
const tone = document.getElementById('tone').value;
const style = document.getElementById('style').value;
const summarizeBtn = document.getElementById('summarize-btn');
const loading = document.getElementById('loading');
if (!inputText) {
document.getElementById('summary-output').innerHTML = 'Please enter some text to summarize.';
document.getElementById('original-text').innerHTML = '';
document.getElementById('key-phrases').innerHTML = '';
return;
}
summarizeBtn.disabled = true;
loading.style.display = 'block';
// Split text into sentences
const sentences = inputText.match(/[^.!?]+[.!?]+/g)?.map(s => s.trim()) || [inputText];
if (sentences.length < 2) {
document.getElementById('summary-output').innerHTML = 'Text is too short to summarize.';
document.getElementById('original-text').innerHTML = '';
document.getElementById('key-phrases').innerHTML = '';
summarizeBtn.disabled = false;
loading.style.display = 'none';
return;
}
// Calculate stats
const words = inputText.split(/\s+/).length;
const readingTime = Math.ceil(words / 200);
const sentenceCount = sentences.length;
// Word and phrase frequency (TF-IDF inspired)
const wordFreq = {};
const phraseFreq = {};
inputText.toLowerCase().split(/\s+/).forEach(word => {
if (word.length > 3 && !['the', 'and', 'is', 'are', 'in', 'to', 'of', 'for'].includes(word)) {
wordFreq[word] = (wordFreq[word] || 0) + 1;
}
});
// Extract key phrases (2-3 word combinations)
const wordsArray = inputText.toLowerCase().split(/\s+/);
for (let i = 0; i < wordsArray.length - 1; i++) {
const phrase = wordsArray.slice(i, i + 2).join(' ');
if (!phrase.includes(' the ') && !phrase.includes(' and ')) {
phraseFreq[phrase] = (phraseFreq[phrase] || 0) + 1;
}
if (i < wordsArray.length - 2) {
const triPhrase = wordsArray.slice(i, i + 3).join(' ');
if (!triPhrase.includes(' the ') && !triPhrase.includes(' and ')) {
phraseFreq[triPhrase] = (phraseFreq[triPhrase] || 0) + 1;
}
}
}
// Score sentences
const sentenceScores = sentences.map((sentence, index) => {
const words = sentence.toLowerCase().split(/\s+/);
let score = words.reduce((sum, word) => sum + (wordFreq[word] || 0) / (1 + Math.log(words.length)), 0);
score += (sentences.length - index) * 0.1; // Position boost
score *= (words.length > 5 && words.length < 30) ? 1 : 0.5; // Length penalty
return { sentence, score, index };
});
// Get top sentences
let topSentences = sentenceScores
.sort((a, b) => b.score - a.score)
.slice(0, Math.min(summaryLength, sentences.length));
// Adjust for word limit
let currentWords = topSentences.reduce((sum, item) => sum + item.sentence.split(/\s+/).length, 0);
while (currentWords > wordLimit && topSentences.length > 1) {
topSentences.pop();
currentWords = topSentences.reduce((sum, item) => sum + item.sentence.split(/\s+/).length, 0);
}
// Apply tone
topSentences = topSentences.map(item => ({
...item,
sentence: adjustTone(item.sentence, tone)
}));
// Format summary
let output = '';
if (style === 'bullets') {
output = '<ul>' + topSentences.map(item => `<li>${item.sentence}</li>`).join('') + '</ul>';
} else {
output = topSentences.map(item => item.sentence).join(' ');
}
// Highlight original text
let highlightedText = inputText;
const topSentenceIndices = topSentences.map(item => item.index);
topSentenceIndices.forEach(index => {
const escapedSentence = sentences[index].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const regex = new RegExp(`(${escapedSentence})`, 'g');
highlightedText = highlightedText.replace(regex, '<span class="highlight">$1</span>');
});
// Extract key phrases
const keyPhrases = Object.entries(phraseFreq)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([phrase]) => phrase)
.join(', ');
// Calculate compression ratio
const summaryWords = topSentences.reduce((sum, item) => sum + item.sentence.split(/\s+/).length, 0);
const compressionRatio = ((words - summaryWords) / words * 100).toFixed(1);
// Update UI
document.getElementById('summary-output').innerHTML = output || 'No summary generated.';
document.getElementById('key-phrases').innerHTML = `<strong>Key Phrases:</strong> ${keyPhrases || 'None identified.'}`;
document.getElementById('original-text').innerHTML = highlightedText.replace(/\n/g, '<br>');
document.getElementById('stats').innerHTML = `Word Count: ${words} | Sentences: ${sentenceCount} | Est. Reading Time: ${readingTime} min | Compression: ${compressionRatio}%`;
// Save to history
saveToHistory(output, keyPhrases);
summarizeBtn.disabled = false;
loading.style.display = 'none';
}
function adjustTone(sentence, tone) {
if (tone === 'formal') {
return sentence.replace(/gonna|kinda|sorta/gi, 'going to').replace(/ ain't/gi, ' is not');
} else if (tone === 'informal') {
return sentence.replace(/going to/gi, 'gonna').replace(/is not/gi, "ain't");
}
return sentence;
}
function copySummary() {
const summary = document.getElementById('summary-output').innerText;
navigator.clipboard.writeText(summary).then(() => {
alert('Summary copied to clipboard!');
}).catch(() => {
alert('Failed to copy summary.');
});
}
function downloadSummary() {
const summary = document.getElementById('summary-output').innerText;
const blob = new Blob([summary], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'Zupr_Summary.txt';
a.click();
URL.revokeObjectURL(url);
}
function clearText() {
document.getElementById('input-text').value = '';
document.getElementById('summary-output').innerHTML = 'Your summary will appear here...';
document.getElementById('key-phrases').innerHTML = 'Key phrases will appear here...';
document.getElementById('original-text').innerHTML = 'Highlighted original text will appear here...';
document.getElementById('stats').innerHTML = '';
}
function saveToHistory(summary, keyPhrases) {
let history = JSON.parse(localStorage.getItem('summaryHistory') || '[]');
history.unshift({ summary, keyPhrases, date: new Date().toLocaleString() });
history = history.slice(0, 5); // Keep last 5
localStorage.setItem('summaryHistory', JSON.stringify(history));
updateHistory();
}
function updateHistory() {
const historyList = document.getElementById('history-list');
const history = JSON.parse(localStorage.getItem('summaryHistory') || '[]');
historyList.innerHTML = history.map((item, index) => `
<li onclick="restoreSummary(${index})">
${item.date}: ${item.summary.substring(0, 100)}...
<br><small>Key Phrases: ${item.keyPhrases.substring(0, 50)}...</small>
</li>
`).join('');
}
function restoreSummary(index) {
const history = JSON.parse(localStorage.getItem('summaryHistory') || '[]');
const item = history[index];
document.getElementById('summary-output').innerHTML = item.summary;
document.getElementById('key-phrases').innerHTML = `<strong>Key Phrases:</strong> ${item.keyPhrases}`;
}
// Initialize history
updateHistory();
// Slider value displays
document.getElementById('summary-length').addEventListener('input', function() {
document.getElementById('length-value').innerText = this.value;
});
document.getElementById('word-limit').addEventListener('input', function() {
document.getElementById('word-limit-value').innerText = this.value;
});
</script>
</body>
</html>
Comments
Post a Comment