diff --git a/internal/httpapi/static/app.js b/internal/httpapi/static/app.js
index 9e396db..77a1b59 100644
--- a/internal/httpapi/static/app.js
+++ b/internal/httpapi/static/app.js
@@ -19,7 +19,37 @@ async function api(path, params){
function appendBatch(arr){ const el=document.getElementById('tail'); const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.appendChild(frag); if(st.atBottom){ el.scrollTop = el.scrollHeight; } }
function prependBatch(arr){ const el=document.getElementById('tail'); const oldTop=el.firstChild; const frag=document.createDocumentFragment(); arr.forEach(m=>{ const div=document.createElement('div'); div.className='msg'; div.innerHTML=lineHTML(m); frag.appendChild(div); processLinks(div); }); el.insertBefore(frag, el.firstChild); if(oldTop){ oldTop.scrollIntoView(); } }
-function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not([data-card])'); links.forEach(a=>{ a.setAttribute('data-card','1'); fetch('/api/linkcard?url='+encodeURIComponent(a.href)).then(r=>r.json()).then(card=>{ if(!card) return; if(card.title||card.description||card.image){ const c=document.createElement('div'); c.className='card'; var html=''; if(card.image){ html += '

'; } html += ''; if(card.title){ html += '
'+escapeHtml(card.title)+'
'; } if(card.description){ html += '
'+escapeHtml(card.description)+'
'; } html += '
'; c.innerHTML = ''+html+'
'; a.parentNode.insertBefore(c, a.nextSibling); } }).catch(()=>{}); }); }
+function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not([data-card])'); links.forEach(a=>{ a.setAttribute('data-card','1');
+ // Fetch and render card
+ fetch('/api/linkcard?url='+encodeURIComponent(a.href)).then(r=>r.json()).then(card=>{
+ if(!card) return;
+ if(card.title||card.description||card.image){
+ const c=document.createElement('div'); c.className='card'; var html='';
+ if(card.image){ html += ''; }
+ html += '';
+ if(card.title){ html += '
'+escapeHtml(card.title)+'
'; }
+ if(card.description){ html += '
'+escapeHtml(card.description)+'
'; }
+ html += '
';
+ const row = document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML = html;
+ c.appendChild(row);
+ // Summary control row
+ const ctrl = document.createElement('div'); ctrl.style.marginTop='.25rem';
+ const btn = document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='\u25B6'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
+ const spinner = document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
+ const sum = document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem';
+ btn.onclick = async ()=>{
+ btn.disabled=true; spinner.textContent='…'; sum.textContent='';
+ try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; }
+ catch(e){ sum.textContent = 'error: '+e; }
+ spinner.textContent=''; btn.disabled=false;
+ };
+ ctrl.appendChild(btn); ctrl.appendChild(spinner);
+ c.appendChild(ctrl);
+ c.appendChild(sum);
+ a.parentNode.insertBefore(c, a.nextSibling);
+ }
+ }).catch(()=>{});
+}); }
async function loadChannels(){ try{ const data = await api('/api/channels'); st.channels = data; renderChannels(); if(data.length>0){ selectChannel(data[0]); } } catch(e){} }
function renderChannels(){ const list=document.getElementById('chanlist'); if(!list) return; list.innerHTML=''; st.channels.forEach(c=>{ const a=document.createElement('a'); a.href='#'; a.textContent=c; a.onclick=(ev)=>{ev.preventDefault(); selectChannel(c)}; if(c===st.current) a.className='active'; list.appendChild(a); }); }