AIprojects / templates /index.html
sayanAIAI's picture
Update templates/index.html
7a36697 verified
raw
history blame
12.7 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Summarizer β€” AI Hub</title>
<meta name="description" content="SummarizeAI β€” fast, private text summarizer." />
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#fbfdff;
--muted:#57706d;
--ink:#0b2320;
--accent: linear-gradient(90deg,#9fd7ff,#89e0c9);
--card-radius:12px;
--maxw:900px;
--ease:cubic-bezier(.2,.9,.3,1);
}
*{box-sizing:border-box}
body{margin:0;font-family:Poppins,system-ui,-apple-system,Segoe UI,Roboto,Arial;background:linear-gradient(180deg,#fbfdff,#fffaf8);color:var(--ink);-webkit-font-smoothing:antialiased}
.page{max-width:var(--maxw);margin:32px auto;padding:20px}
header{display:flex;align-items:center;justify-content:space-between;gap:12px}
.brand{display:flex;gap:12px;align-items:center}
.logo{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,#9fd7ff,#c9f0eb);display:flex;align-items:center;justify-content:center;color:#043033;font-weight:700}
h1{margin:0;font-size:1.05rem}
.sub{color:var(--muted);font-size:0.92rem;margin-top:6px}
.card{margin-top:18px;background:linear-gradient(180deg,#ffffff,#fbfbff);border-radius:var(--card-radius);padding:18px;border:1px solid rgba(10,12,12,0.04);box-shadow:0 20px 40px rgba(10,18,16,0.04)}
.controls{display:flex;gap:10px;align-items:center;justify-content:space-between;flex-wrap:wrap}
.left-controls{display:flex;gap:8px;align-items:center}
.select, .btn, .small-btn, .checkbox {
border-radius:10px;padding:10px 12px;border:1px solid rgba(10,12,12,0.04);background:transparent;color:inherit;font-weight:600;
}
.btn{background:linear-gradient(90deg,#9fd7ff,#89e0c9);color:#043033;cursor:pointer;border:0}
.small-btn{font-size:0.9rem;padding:8px 10px;cursor:pointer}
.input-row{display:flex;gap:12px;margin-top:12px;align-items:flex-start}
textarea#text-input{flex:1;min-height:260px;resize:vertical;padding:14px;border-radius:10px;border:1px solid rgba(10,12,12,0.04);background:transparent;color:inherit;font-size:1rem;line-height:1.5}
.side{width:260px;min-width:220px}
.meta{color:var(--muted);font-size:0.9rem;margin-top:8px}
.word-count{color:var(--muted);font-size:0.9rem;margin-top:8px}
#loader{display:none;margin:12px auto;border-radius:50%;width:36px;height:36px;border:4px solid rgba(0,0,0,0.06);border-top-color:#89e0c9;animation:spin 1s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.summary-box{margin-top:16px;background:linear-gradient(180deg,#ffffff,#fbfbff);padding:14px;border-radius:10px;border:1px solid rgba(10,12,12,0.04)}
.summary-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}
.summary-text{white-space:pre-wrap;font-size:1rem;color:var(--ink)}
.history{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}
.pill{background:rgba(0,0,0,0.02);padding:8px 10px;border-radius:999px;border:1px solid rgba(0,0,0,0.03);font-size:0.9rem;color:var(--muted);cursor:pointer}
.msg{margin-top:10px;padding:10px;border-radius:8px;font-weight:600}
.error{background:linear-gradient(180deg, rgba(255,40,60,0.06), rgba(255,40,60,0.02));color:#b80000;border:1px solid rgba(255,40,60,0.06)}
@media (max-width:860px){.input-row{flex-direction:column}.side{width:100%}}
</style>
</head>
<body>
<div class="page" role="main">
<header>
<div class="brand">
<div class="logo">AI</div>
<div>
<h1>Summarizer</h1>
<div class="sub">Fast, private summaries β€” paste or send demo text from the hub.</div>
</div>
</div>
<div>
<button id="themeToggle" class="small-btn">Toggle</button>
</div>
</header>
<div class="card" aria-live="polite">
<div class="controls">
<div class="left-controls">
<label>
<select id="length" class="select" aria-label="Summary length">
<option value="short">Short</option>
<option value="medium" selected>Medium</option>
<option value="long">Long</option>
</select>
</label>
<label>
<select id="tone" class="select" aria-label="Tone">
<option value="neutral" selected>Neutral</option>
<option value="formal">Formal</option>
<option value="casual">Casual</option>
<option value="bullet">Bulleted</option>
</select>
</label>
<label style="display:flex;align-items:center;gap:8px">
<input id="aiChoose" type="checkbox" class="checkbox" /> <span style="font-weight:700">AI choose settings</span>
</label>
<button id="exampleBtn" class="small-btn" title="Insert example">Example</button>
<button id="clearBtn" class="small-btn" title="Clear input">Clear</button>
</div>
<div style="display:flex;gap:8px;align-items:center">
<button id="summarize-btn" class="btn">Summarize</button>
</div>
</div>
<div class="input-row">
<textarea id="text-input" placeholder="Paste your article, meeting notes, or long text here..." aria-label="Text input"></textarea>
<aside class="side" aria-hidden>
<div class="meta">Quick tips</div>
<div style="color:var(--muted);margin-top:8px">β€’ Best with 50–1500 words. Use AI choose for the assistant to pick length & tone.</div>
<div class="word-count" id="word-count">Words: 0</div>
<div style="margin-top:12px" id="wordWarning" class="msg" style="display:none"></div>
<div style="margin-top:14px;font-size:0.95rem;color:var(--muted)">Saved history</div>
<div class="history" id="history"></div>
</aside>
</div>
<div id="loader" role="status" aria-hidden="true"></div>
<div id="error-box" class="msg error" style="display:none"></div>
<div id="summary-area" style="display:none">
<div class="summary-box" role="region" aria-label="Summary result">
<div class="summary-text" id="summary-text"></div>
<div class="summary-actions">
<button id="copy-btn" class="small-btn">Copy</button>
<button id="download-btn" class="small-btn">Download .txt</button>
<button id="save-btn" class="small-btn">Save</button>
</div>
</div>
</div>
</div>
</div>
<script>
/* Config */
const API_ENDPOINT = '/summarize';
const textInput = document.getElementById('text-input');
const summarizeBtn = document.getElementById('summarize-btn');
const loader = document.getElementById('loader');
const summaryArea = document.getElementById('summary-area');
const summaryText = document.getElementById('summary-text');
const copyBtn = document.getElementById('copy-btn');
const downloadBtn = document.getElementById('download-btn');
const saveBtn = document.getElementById('save-btn');
const wordCountEl = document.getElementById('word-count');
const wordWarning = document.getElementById('wordWarning');
const errorBox = document.getElementById('error-box');
const historyEl = document.getElementById('history');
const lengthSelect = document.getElementById('length');
const toneSelect = document.getElementById('tone');
const aiChoose = document.getElementById('aiChoose');
let currentSummary = '';
/* Word count */
function updateWordCount(){
const words = textInput.value.trim().split(/\s+/).filter(Boolean).length;
wordCountEl.textContent = `Words: ${words}`;
if(words < 10){
wordWarning.textContent = 'Please enter at least 10 words to get a good summary.';
wordWarning.style.display = 'block';
} else {
wordWarning.style.display = 'none';
}
}
textInput.addEventListener('input', updateWordCount);
updateWordCount();
/* Small UI helpers */
function showLoader(on=true){
loader.style.display = on ? 'block' : 'none';
}
function showError(msg){
errorBox.textContent = msg;
errorBox.style.display = 'block';
}
function clearError(){
errorBox.style.display = 'none';
}
/* Fetch summary */
async function fetchSummary(text){
const body = {
text,
length: aiChoose.checked ? "auto" : lengthSelect.value,
tone: aiChoose.checked ? "auto" : toneSelect.value
};
showError(''); clearError();
summaryArea.style.display = 'none';
showLoader(true);
summarizeBtn.disabled = true;
try{
const res = await fetch(API_ENDPOINT, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(body)
});
if(!res.ok) throw new Error(`Server error: ${res.status}`);
const data = await res.json();
currentSummary = data.summary || data?.result || '';
summaryText.textContent = currentSummary;
summaryArea.style.display = 'block';
// save lightweight meta if available
if(data.meta) {
const m = document.createElement('div');
m.textContent = `Settings: ${data.meta.length_choice || ''} Β· ${data.meta.tone || ''} Β· ${data.meta.time_seconds || 0}s`;
m.style.color = 'var(--muted)';
m.style.fontSize = '0.85rem';
m.style.marginTop = '8px';
if(!summaryArea.querySelector('.meta-info')) { m.className = 'meta-info'; summaryArea.querySelector('.summary-box').appendChild(m); }
}
}catch(err){
showError(err.message || 'Failed to summarize. Try again later.');
}finally{
showLoader(false);
summarizeBtn.disabled = false;
}
}
/* Buttons */
document.getElementById('exampleBtn').addEventListener('click', ()=> {
textInput.value = "OpenAI launched a new model today that focuses on making text summarization far more accurate for long-form content. Engineers and researchers praised the model's efficiency in tests.";
updateWordCount();
});
document.getElementById('clearBtn').addEventListener('click', ()=> { textInput.value=''; updateWordCount(); });
summarizeBtn.addEventListener('click', (e)=> {
e.preventDefault();
const txt = textInput.value.trim();
if(!txt || txt.split(/\s+/).filter(Boolean).length < 10){
wordWarning.style.display = 'block';
return;
}
fetchSummary(txt);
});
copyBtn.addEventListener('click', ()=> {
if(!currentSummary) return;
navigator.clipboard.writeText(currentSummary).then(()=> {
copyBtn.textContent = 'Copied!';
setTimeout(()=> copyBtn.textContent = 'Copy',1400);
});
});
downloadBtn.addEventListener('click', ()=> {
if(!currentSummary) return;
const blob = new Blob([currentSummary], {type:'text/plain;charset=utf-8'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'summary.txt'; document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
});
saveBtn.addEventListener('click', ()=> {
if(!currentSummary) return;
const item = {text: textInput.value, summary: currentSummary, time: Date.now()};
const list = JSON.parse(localStorage.getItem('sa_history') || '[]');
list.unshift(item);
localStorage.setItem('sa_history', JSON.stringify(list.slice(0,20)));
renderHistory();
});
/* History */
function renderHistory(){
const list = JSON.parse(localStorage.getItem('sa_history') || '[]');
historyEl.innerHTML = '';
list.forEach((it, idx)=> {
const pill = document.createElement('button');
pill.className = 'pill';
pill.textContent = new Date(it.time).toLocaleString();
pill.title = (it.summary || '').slice(0,200);
pill.addEventListener('click', ()=> {
textInput.value = it.text; updateWordCount();
summaryText.textContent = it.summary; summaryArea.style.display = 'block';
});
historyEl.appendChild(pill);
});
}
renderHistory();
/* Accept prefill from hub (postMessage) */
window.addEventListener('message', (ev)=> {
try{
const d = ev.data || {};
if(d && d.type === 'summarizer:prefill' && typeof d.text === 'string'){
textInput.value = d.text;
updateWordCount();
}
}catch(e){}
}, false);
/* Support ?text= param */
(function prefillFromQuery(){
try{
const params = new URLSearchParams(location.search);
const t = params.get('text');
if(t){ textInput.value = t; updateWordCount(); }
}catch(e){}
})();
/* Theme toggle (minimal) */
document.getElementById('themeToggle').addEventListener('click', ()=> {
document.body.classList.toggle('light');
});
/* accessibility: Ctrl/Cmd+Enter to submit */
textInput.addEventListener('keydown', (e)=> { if(e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); summarizeBtn.click(); }});
</script>
</body>
</html>