Scripts/news/static/view.html

276 lines
8.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Article View</title>
<style>
:root{
/* future per-paragraph background color */
--card-bg: #ffffff;
}
/* ─────────── Global title bar ─────────── */
.navbar{
position:fixed;
top:0; left:0; right:0;
height:3rem;
background:#0d47a1;
color:#fff;
display:flex;
align-items:center;
justify-content:space-between;
padding:0 1rem;
box-shadow:0 1px 4px rgba(0,0,0,.15);
z-index:100;
}
.navbar .brand{
font-size:1.15rem;
font-weight:700;
color:#fff;
text-decoration:none;
}
.navbar .nav-link{
color:#fff;
text-decoration:none;
margin-left:1rem;
font-size:.95rem;
}
.navbar .nav-link:hover{
text-decoration:underline;
}
body{
font-family: Arial, sans-serif;
margin:0;
padding: 4rem 1rem 2rem;
background:#f6f8fa;
color:#222;
}
h1{
text-align:center;
margin:1.5rem 0 1rem;
font-size:1.6rem;
}
a,
a:visited,
a:hover,
a:active,
a:focus {
/*text-decoration: none;*/
color: inherit; /* keep the original text color */
}
/* TOPICS GRID ------------------------------------------------------- */
#topics{
display:grid;
grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
gap:.75rem;
max-width:1200px;
margin:0 auto 2rem;
list-style:none;
padding:0;
}
.topic-chip{
background:#e8eef6;
border-radius:4px;
padding:.6rem .8rem;
font-size:.9rem;
line-height:1.3;
}
/* SUMMARY BLOB ------------------------------------------------------ */
#summary{
background:#fff8e6;
border-left:5px solid #f5c147;
border-radius:4px;
padding:1rem 1.2rem;
max-width:900px;
margin:0 auto 2rem;
font-size:1rem;
}
/* PARAGRAPH CARDS --------------------------------------------------- */
#paragraphs{
display:flex;
flex-direction:column;
gap:1rem;
max-width:900px;
margin:0 auto;
}
/* make each paragraph a little flex-box so we can place
the coloured badge on the right-hand side */
.paragraph-card{
display:flex;
justify-content:space-between;
align-items:flex-start;
gap:.75rem;
background:var(--card-bg);
border-radius:6px;
padding:1rem 1.2rem;
box-shadow:0 1px 3px rgba(0,0,0,.08);
transition:box-shadow .15s ease;
}
.paragraph-card:hover{
box-shadow:0 3px 8px rgba(0,0,0,.14);
}
/* ─────────── Relevance badge ─────────── */
.relevance-badge{
min-width:72px;
font-size:.75rem;
font-weight:600;
line-height:1.4;
color:#fff;
padding:.25rem .4rem;
border-radius:4px;
text-align:center;
user-select:none;
flex-shrink:0;
}
.relevance-low {background:#d32f2f;} /* red */
.relevance-med {background:#f57c00;} /* amber */
.relevance-high {background:#388e3c;} /* green */
.error{
color:#c00;
text-align:center;
margin-top:2rem;
}
</style>
</head>
<body>
<header class="navbar">
<a href="/" class="brand">Newsulizer</a>
<nav>
<a href="/" class="nav-link">Home</a>
</nav>
</header>
<div style="display: flex; justify-content: center;">
<h1 id="title"><b></b></h1>
</div>
<div style="display: flex; justify-content: center;">
<div style="display: block; padding-bottom: 1rem;">
<div style="display: flex; justify-content: center;">
<h2>Article Paragraphs</h2>
</div>
<p>Extracted from the original article</p>
</div>
</div>
<section id="paragraphs" aria-label="Article paragraphs"></section>
<div style="display: flex; justify-content: center;">
<h2>Article Topics (AI Generated)</h2>
</div>
<ul id="topics" aria-label="Article topics"></ul>
<div style="display: flex; justify-content: center;">
<h2>Article Summary (AI Generated)</h2>
</div>
<section id="summary" hidden></section>
<p id="error" class="error" hidden>Unable to load article. Please try again later.</p>
<script type="text/javascript">
(function main(){
const qs = new URLSearchParams(window.location.search);
const url = qs.get('url');
const API = '/api/view_article?url=' + encodeURIComponent(url ?? '');
const elTopics = document.getElementById('topics');
const elSummary = document.getElementById('summary');
const elParagraphs = document.getElementById('paragraphs');
const elError = document.getElementById('error');
const elTitle = document.getElementById('title');
if(!url){
elError.hidden = false;
elError.textContent = '`url` query parameter missing.';
return;
}
fetch(API)
.then(r => {
if(!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(renderArticle)
.catch(err => {
console.error(err);
elError.hidden = false;
});
/* ---------------------------------------------------------------- */
function renderArticle(data){
elTitle.innerHTML = "<a href='" + url + "'>\"" + data.title + "\"</a>";
/* 1. TOPICS -------------------------------------------------- */
if(Array.isArray(data.topics)){
data.topics.forEach(topic => {
const li = document.createElement('li');
li.className = 'topic-chip';
li.textContent = topic;
elTopics.appendChild(li);
});
}
/* 2. SUMMARY ------------------------------------------------- */
if(typeof data.summary === 'string' && data.summary.trim() !== ''){
elSummary.textContent = data.summary.trim();
elSummary.hidden = false;
}
/* 3. PARAGRAPHS --------------------------------------------- */
Object.entries(data.paragraphs ?? {}).forEach(([pid, pData]) => {
const card = document.createElement('div');
card.className = 'paragraph-card';
/* main paragraph text */
const p = document.createElement('p');
p.textContent = pData.text;
p.style.margin = 0;
card.appendChild(p);
/* relevance badge --------------------------------------- */
const badge = document.createElement('span');
badge.classList.add('relevance-badge');
/* fall-back to 0 if the API did not supply a number */
const summary_rating = Number.parseFloat(pData.summary_rating ?? 0) / 100;
const ratingsArr = pData.topic_ratings ?? [];
const totalTrue = ratingsArr.reduce(
(sum, r) => sum + (r.rating ? 1 : 0),
0
);
const topic_ratings = totalTrue / ratingsArr.length;
const rating = (summary_rating + topic_ratings) / 2;
let cssClass = 'relevance-low';
if (rating >= 0.66) cssClass = 'relevance-high';
else if (rating >= 0.33) cssClass = 'relevance-med';
badge.classList.add(cssClass);
/* display as percentage for clarity */
const pct = Math.round(rating * 100);
badge.textContent = pct + '% relevant';
card.appendChild(badge);
/* store ratings for possible later use */
card.dataset.summaryRating = pData.summary_rating ?? '';
card.dataset.topicRatings = JSON.stringify(
(pData.topic_ratings ?? []).map(r => !!r.rating)
);
elParagraphs.appendChild(card);
});
}
})();
</script>
</body>
</html>