UX: Résumés RSS par article + Optimisation 1920x1080

Résumés RSS par article (100 mots max):
- Modifié route POST /api/rss/summarize pour générer un résumé PAR article
- Chaque résumé limité à 100 mots (max_tokens: 200)
- Format: **Titre** + Résumé + 🔗 [Lire l'article](lien)
- Résumés générés en parallèle pour meilleure performance
- Supprimé requireAdmin pour accès à tous utilisateurs authentifiés
- Timeout 30s par article au lieu de 60s global

Frontend - Affichage résumés:
- renderRssSummaries() : Détection auto ancien/nouveau format
- formatSummary() : Support liens markdown [texte](url)
- summarizeRss() : Gère tableau de résumés au lieu d'un seul
- Styles CSS pour .rss-link (hover avec underline)
- Nouveau style .rss-summary-article pour résumés individuels

Optimisation pour écran 1920x1080:
- max-width: 1400px → 1800px (utilise mieux l'espace horizontal)
- todo-box-width: 340px → 420px (+80px de largeur)
- Boxes TODO et RSS élargies de 23%
- Grille: ~960px notes + 420px todo + 420px rss = 1800px
- Layout optimisé pour grand écran

CSS - Nouveaux styles:
- .rss-link : liens cliquables avec hover effect
- .rss-summary-article : carte compacte pour résumé individuel
- Hover effects sur liens et cartes
- Responsive maintenu pour petits écrans

Impact utilisateur:
✓ Un résumé par article avec lien direct
✓ Plus concis (100 mots max vs résumé global)
✓ Interface plus large et spacieuse sur 1920x1080
✓ Boxes TODO et RSS 23% plus larges
✓ Meilleure lisibilité des contenus

Files:
- routes/rss.routes.js: Nouveau système de résumés par article
- public/js/complete-app.js: Rendu et gestion résumés
- public/css/variables.css: Dimensions optimisées 1920x1080
- public/css/layout.css: Styles liens + résumés individuels
This commit is contained in:
Claude committed 2025-11-11 18:37:40 +00:00
1 parent 852d631c25
commit 73391def6c
4 files changed
+173 -84

No files matched your search

+43
View File
@@ -551,6 +551,49 @@ body {
font-style: italic;
}
.rss-summary-content a.rss-link {
color: var(--color-accent);
text-decoration: none;
font-weight: var(--font-weight-medium);
transition: all var(--transition-fast);
border-bottom: 1px solid transparent;
}
.rss-summary-content a.rss-link:hover {
border-bottom-color: var(--color-accent);
opacity: 0.8;
}
/* Nouveau style pour résumés par article */
.rss-summary-article {
padding: var(--spacing-md);
background: var(--color-background);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
margin-bottom: var(--spacing-md);
transition: all var(--transition-fast);
}
.rss-summary-article:hover {
border-color: var(--color-accent);
box-shadow: var(--shadow-sm);
}
.rss-summary-article .rss-summary-content {
font-size: var(--font-size-sm);
line-height: 1.6;
}
.rss-summary-article .rss-summary-meta {
margin-top: var(--spacing-sm);
padding-top: var(--spacing-sm);
border-top: 1px solid var(--color-border);
display: flex;
gap: var(--spacing-md);
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}
/* Responsive */
@media (max-width: 1200px) {
.content-container {
+3 -3
View File
@@ -67,12 +67,12 @@
--transition-base: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
--transition-slow: 0.35s cubic-bezier(0.4, 0, 0.2, 1);
/* Dimensions */
/* Dimensions - Optimisé pour 1920x1080 */
--header-height: 70px;
--max-width: 1400px;
--max-width: 1800px;
--note-width: 320px;
--note-expanded-width: 680px;
--todo-box-width: 340px;
--todo-box-width: 420px;
/* Z-index */
--z-header: 100;
+56 -23
View File
@@ -1569,18 +1569,37 @@ function renderRssSummaries(summaries) {
return;
}
container.innerHTML = summaries.map(summary => `
<div class="rss-summary">
<div class="rss-summary-header">
<div class="rss-summary-title">📰 Résumé ${new Date(summary.created_at).toLocaleDateString('fr-FR')}</div>
<div class="rss-summary-meta">
<span>${summary.articles_count} articles</span>
${summary.model ? `<span>${escapeHtml(summary.model.split('/')[1] || summary.model)}</span>` : ''}
// Nouvelle structure : afficher les résumés PAR ARTICLE avec titre + résumé + lien
container.innerHTML = summaries.map(summary => {
// Le résumé peut être soit l'ancien format (global) soit le nouveau (par article depuis DB)
// Si c'est un résumé DB, il contient déjà le titre et le lien formatés
if (summary.summary && summary.summary.includes('🔗')) {
// Ancien format sauvegardé en DB avec titre et lien
return `
<div class="rss-summary rss-summary-article">
<div class="rss-summary-content">${formatSummary(summary.summary)}</div>
<div class="rss-summary-meta">
${summary.created_at ? `<span>${new Date(summary.created_at).toLocaleDateString('fr-FR', { hour: '2-digit', minute: '2-digit' })}</span>` : ''}
${summary.model ? `<span>${escapeHtml(summary.model.split('/')[1] || summary.model)}</span>` : ''}
</div>
</div>
</div>
<div class="rss-summary-content">${formatSummary(summary.summary)}</div>
</div>
`).join('');
`;
} else {
// Ancien format (résumé global)
return `
<div class="rss-summary">
<div class="rss-summary-header">
<div class="rss-summary-title">📰 Résumé ${new Date(summary.created_at).toLocaleDateString('fr-FR')}</div>
<div class="rss-summary-meta">
<span>${summary.articles_count} articles</span>
${summary.model ? `<span>${escapeHtml(summary.model.split('/')[1] || summary.model)}</span>` : ''}
</div>
</div>
<div class="rss-summary-content">${formatSummary(summary.summary)}</div>
</div>
`;
}
}).join('');
}
function formatSummary(summary) {
@@ -1598,12 +1617,18 @@ function formatSummary(summary) {
// Listes numérotées
html = html.replace(/\n\d+\. (.*?)(?=\n|$)/g, '<li>$1</li>');
// Gras
// Gras (avant les liens pour éviter les conflits)
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Italique
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Liens markdown [texte](url) - convertir en liens cliquables
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="rss-link">$1</a>');
// Emoji lien avec texte - gérer 🔗 suivi d'espace
html = html.replace(/🔗\s*/g, '🔗 ');
// Sauts de ligne
html = html.replace(/\n\n/g, '<br><br>');
html = html.replace(/\n/g, '<br>');
@@ -1628,21 +1653,29 @@ async function summarizeRss() {
if (summaryEnabled) {
// Si les résumés sont activés, juste recharger le bloc RSS
alert('Résumé généré avec succès !');
alert(`${result.articles_count} résumés générés avec succès !`);
await loadRssArticles();
} else {
// Sinon créer une note avec le résumé
const newNote = await api.post('/api/notes', {
title: `📰 Résumé RSS - ${new Date().toLocaleDateString('fr-FR')}`,
content: result.summary
});
// Sinon créer une note avec TOUS les résumés par article
if (result.summaries && result.summaries.length > 0) {
const allSummaries = result.summaries.map(s =>
`**${s.title}**\n_${s.feed_title} - ${new Date(s.pub_date).toLocaleDateString('fr-FR')}_\n\n${s.summary}\n\n🔗 [Lire l'article](${s.link})`
).join('\n\n---\n\n');
alert('Résumé généré et sauvegardé dans une nouvelle note !');
await loadNotes();
const newNote = await api.post('/api/notes', {
title: `📰 Résumés RSS - ${new Date().toLocaleDateString('fr-FR')}`,
content: allSummaries
});
// Expand the new note
state.expandedNoteId = newNote.id;
renderNotes();
alert(`${result.articles_count} résumés générés et sauvegardés dans une nouvelle note !`);
await loadNotes();
// Expand the new note
state.expandedNoteId = newNote.id;
renderNotes();
} else {
alert('Aucun résumé généré');
}
}
} catch (error) {
console.error('Erreur génération résumé RSS:', error);