ux(cards): place 🌝/🌚 and chevron inline with original link; collapse hides entire card; improve YouTube summarization path groundwork
This commit is contained in:
parent
575622b45c
commit
fed806bfc0
3 changed files with 37 additions and 44 deletions
|
|
@ -37,21 +37,6 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
|
||||||
if(!card) return;
|
if(!card) return;
|
||||||
if(card.title||card.description||card.image||card.html){
|
if(card.title||card.description||card.image||card.html){
|
||||||
const c=document.createElement('div'); c.className='card';
|
const c=document.createElement('div'); c.className='card';
|
||||||
// Header with favicon/domain + title (clickable), actions on the right
|
|
||||||
const head=document.createElement('div'); head.className='card-head';
|
|
||||||
const left=document.createElement('div'); left.style.display='flex'; left.style.alignItems='center'; left.style.gap='.5rem';
|
|
||||||
const u=new URL(a.href);
|
|
||||||
const fav=document.createElement('img'); fav.src='https://www.google.com/s2/favicons?domain='+u.hostname+'&sz=32'; fav.width=16; fav.height=16; fav.alt=''; fav.loading='lazy'; fav.referrerPolicy='no-referrer';
|
|
||||||
const title=document.createElement('a'); title.href=a.href; title.target='_blank'; title.rel='noopener'; title.textContent=card.title||u.hostname; title.style.fontWeight='600'; title.style.textDecoration='none';
|
|
||||||
left.appendChild(fav); left.appendChild(title);
|
|
||||||
const actions=document.createElement('div'); actions.className='card-actions';
|
|
||||||
const chevron=document.createElement('button'); chevron.type='button'; chevron.title='Expand/collapse'; chevron.textContent='▾'; chevron.style.padding='0 .4rem'; chevron.style.fontSize='.9rem';
|
|
||||||
const btn=document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='🌝'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
|
|
||||||
const spinner=document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
|
|
||||||
actions.appendChild(btn); actions.appendChild(chevron); actions.appendChild(spinner);
|
|
||||||
head.appendChild(left); head.appendChild(actions);
|
|
||||||
c.appendChild(head);
|
|
||||||
// Body (details)
|
|
||||||
const details=document.createElement('div'); details.className='card-details';
|
const details=document.createElement('div'); details.className='card-details';
|
||||||
var html='';
|
var html='';
|
||||||
if(card.image){ html += '<div><img src="'+card.image+'" alt="" style="max-width:160px;max-height:120px;object-fit:cover;border-radius:.25rem"></div>'; }
|
if(card.image){ html += '<div><img src="'+card.image+'" alt="" style="max-width:160px;max-height:120px;object-fit:cover;border-radius:.25rem"></div>'; }
|
||||||
|
|
@ -61,28 +46,30 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
const row=document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML=html;
|
const row=document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML=html;
|
||||||
details.appendChild(row);
|
details.appendChild(row);
|
||||||
details.querySelectorAll('img').forEach(img=> img.addEventListener('load', ()=> pinBottomMulti()));
|
|
||||||
if(card.html){ const wrap=document.createElement('div'); wrap.innerHTML=card.html; details.appendChild(wrap); ensureTwitterWidgets(); }
|
if(card.html){ const wrap=document.createElement('div'); wrap.innerHTML=card.html; details.appendChild(wrap); ensureTwitterWidgets(); }
|
||||||
|
details.querySelectorAll('img').forEach(img=> img.addEventListener('load', ()=> pinBottomMulti()));
|
||||||
c.appendChild(details);
|
c.appendChild(details);
|
||||||
// Summary block (hidden until requested)
|
const sum=document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem'; sum.style.display='none'; c.appendChild(sum);
|
||||||
const sum=document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem'; sum.style.display='none';
|
// Place actions inline next to the original link
|
||||||
c.appendChild(sum);
|
const act=document.createElement('span'); act.style.marginLeft='.35rem';
|
||||||
|
const sumBtn=document.createElement('button'); sumBtn.type='button'; sumBtn.title='Summarize'; sumBtn.textContent='🌝'; sumBtn.style.padding='0 .35rem'; sumBtn.style.fontSize='.9rem';
|
||||||
const toggleDetails = ()=>{ const collapsed = details.style.display==='none'; details.style.display = collapsed? '' : 'none'; chevron.textContent = collapsed? '▾':'▸'; pinBottomMulti(); };
|
const chevron=document.createElement('button'); chevron.type='button'; chevron.title='Expand/collapse'; chevron.textContent='▾'; chevron.style.padding='0 .35rem'; chevron.style.fontSize='.9rem';
|
||||||
head.addEventListener('click', (ev)=>{ if(ev.target===btn || actions.contains(ev.target)) return; toggleDetails(); });
|
const spinner=document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.25rem';
|
||||||
chevron.onclick=(ev)=>{ ev.stopPropagation(); toggleDetails(); };
|
act.appendChild(sumBtn); act.appendChild(chevron); act.appendChild(spinner);
|
||||||
|
a.insertAdjacentElement('afterend', act);
|
||||||
btn.onclick = async ()=>{
|
// Insert card after the link line
|
||||||
if(sum.style.display!== 'none' && sum.textContent){ // hide existing
|
a.parentNode.insertBefore(c, act.nextSibling);
|
||||||
sum.style.display='none'; btn.textContent='🌝'; pinBottomMulti(); return;
|
const toggle = ()=>{ const hidden = c.style.display==='none'; c.style.display = hidden? '' : 'none'; chevron.textContent = hidden? '▾':'▸'; pinBottomMulti(); };
|
||||||
}
|
chevron.onclick = (ev)=>{ ev.stopPropagation(); toggle(); };
|
||||||
btn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display='';
|
sumBtn.onclick = async ()=>{
|
||||||
try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; btn.textContent='🌚'; }
|
if(sum.style.display!== 'none' && sum.textContent){ sum.style.display='none'; sumBtn.textContent='🌝'; pinBottomMulti(); return; }
|
||||||
|
// Ensure card is visible when showing summary
|
||||||
|
if(c.style.display==='none'){ toggle(); }
|
||||||
|
sumBtn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display='';
|
||||||
|
try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; sumBtn.textContent='🌚'; }
|
||||||
catch(e){ sum.textContent = 'error: '+e; }
|
catch(e){ sum.textContent = 'error: '+e; }
|
||||||
spinner.textContent=''; btn.disabled=false; pinBottomMulti();
|
spinner.textContent=''; sumBtn.disabled=false; pinBottomMulti();
|
||||||
};
|
};
|
||||||
|
|
||||||
a.parentNode.insertBefore(c, a.nextSibling);
|
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
}); }
|
}); }
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,9 @@ func (o *OpenAI) SummarizeLink(ctx context.Context, rawURL string) (string, erro
|
||||||
}
|
}
|
||||||
text = strings.ReplaceAll(text, "\r", "")
|
text = strings.ReplaceAll(text, "\r", "")
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
if len(text) > 6000 { text = text[:6000] }
|
if len(text) > 6000 {
|
||||||
|
text = text[:6000]
|
||||||
|
}
|
||||||
content = text
|
content = text
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
@ -237,11 +239,17 @@ func (o *OpenAI) SummarizeLink(ctx context.Context, rawURL string) (string, erro
|
||||||
},
|
},
|
||||||
MaxCompletionTokens: o.maxTokens,
|
MaxCompletionTokens: o.maxTokens,
|
||||||
}
|
}
|
||||||
if !reasoningLike { req.Temperature = 0.2 }
|
if !reasoningLike {
|
||||||
|
req.Temperature = 0.2
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.CreateChatCompletion(ctx, req)
|
resp, err := client.CreateChatCompletion(ctx, req)
|
||||||
if err != nil { return "", err }
|
if err != nil {
|
||||||
if len(resp.Choices) == 0 { return "", nil }
|
return "", err
|
||||||
|
}
|
||||||
|
if len(resp.Choices) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
return strings.TrimSpace(resp.Choices[0].Message.Content), nil
|
return strings.TrimSpace(resp.Choices[0].Message.Content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
package summarizer
|
package summarizer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sojuboy/internal/store"
|
"sojuboy/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Summarizer interface {
|
type Summarizer interface {
|
||||||
Summarize(ctx context.Context, channel string, msgs []store.Message, window time.Duration) (string, error)
|
Summarize(ctx context.Context, channel string, msgs []store.Message, window time.Duration) (string, error)
|
||||||
SummarizeLink(ctx context.Context, rawURL string) (string, error)
|
SummarizeLink(ctx context.Context, rawURL string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue