Feature: Amélioration système RSS et OpenRouter

Backend:
- Récupération des vrais modèles depuis l'API OpenRouter
- Table rss_summaries pour stocker les résumés générés
- Limitation à 5 articles RSS au lieu de 10
- Route GET /api/rss/summaries pour récupérer les résumés
- Sauvegarde automatique des résumés en base de données

Frontend:
- Affichage conditionnel: résumés si activé, sinon articles
- Fonction renderRssSummaries() avec conversion Markdown→HTML
- Formatage du résumé (titres, listes, gras, italique)
- CSS complet pour .rss-summary avec typography
- Bouton résumer: crée note ou recharge bloc selon config

Comportement:
- Si rss_summary_enabled=1: affiche les 5 derniers résumés
- Sinon: affiche les 5 derniers articles
- Résumés formatés avec Markdown dans le bloc RSS
This commit is contained in:
Claude committed 2025-11-11 17:03:44 +00:00
1 parent 7dd01f6113
commit 9748ecb1bb
4 files changed
+236 -36

No files matched your search

+67
View File
@@ -484,6 +484,73 @@ body {
overflow: hidden;
}
.rss-summary {
padding: var(--spacing-lg);
background: var(--color-background);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
margin-bottom: var(--spacing-md);
}
.rss-summary-header {
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--color-border);
}
.rss-summary-title {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-xs);
}
.rss-summary-meta {
display: flex;
gap: var(--spacing-md);
font-size: var(--font-size-xs);
color: var(--color-text-secondary);
}
.rss-summary-content {
font-size: var(--font-size-sm);
color: var(--color-text-primary);
line-height: 1.6;
}
.rss-summary-content h2 {
font-size: var(--font-size-lg);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin: var(--spacing-md) 0 var(--spacing-sm) 0;
}
.rss-summary-content h3 {
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
margin: var(--spacing-sm) 0 var(--spacing-xs) 0;
}
.rss-summary-content ul,
.rss-summary-content ol {
margin: var(--spacing-sm) 0;
padding-left: var(--spacing-xl);
}
.rss-summary-content li {
margin-bottom: var(--spacing-xs);
}
.rss-summary-content strong {
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
}
.rss-summary-content em {
font-style: italic;
}
/* Responsive */
@media (max-width: 1200px) {
.content-container {
+84 -12
View File
@@ -1486,8 +1486,19 @@ async function fetchRssArticles() {
async function loadRssArticles() {
try {
const articles = await api.get('/api/rss/articles');
renderRssArticles(articles);
// Vérifier si les résumés sont activés
const settings = await api.get('/api/settings');
const summaryEnabled = settings.rss_summary_enabled === '1' || settings.rss_summary_enabled === 'true';
if (summaryEnabled) {
// Charger les résumés
const summaries = await api.get('/api/rss/summaries');
renderRssSummaries(summaries);
} else {
// Charger les articles normaux
const articles = await api.get('/api/rss/articles');
renderRssArticles(articles);
}
} catch (error) {
console.error('Erreur chargement articles RSS:', error);
}
@@ -1514,6 +1525,57 @@ function renderRssArticles(articles) {
`).join('');
}
function renderRssSummaries(summaries) {
const container = document.getElementById('rssArticlesList');
if (!container) return;
if (!summaries || summaries.length === 0) {
container.innerHTML = '<p class="rss-empty">Aucun résumé pour le moment</p>';
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>` : ''}
</div>
</div>
<div class="rss-summary-content">${formatSummary(summary.summary)}</div>
</div>
`).join('');
}
function formatSummary(summary) {
// Convertir le Markdown de base en HTML
let html = escapeHtml(summary);
// Titres
html = html.replace(/\n## (.*?)\n/g, '<h3>$1</h3>');
html = html.replace(/\n# (.*?)\n/g, '<h2>$1</h2>');
// Listes à puces
html = html.replace(/\n- (.*?)(?=\n|$)/g, '<li>$1</li>');
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
// Listes numérotées
html = html.replace(/\n\d+\. (.*?)(?=\n|$)/g, '<li>$1</li>');
// Gras
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Italique
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
// Sauts de ligne
html = html.replace(/\n\n/g, '<br><br>');
html = html.replace(/\n/g, '<br>');
return html;
}
async function summarizeRss() {
const btn = document.getElementById('summarizeRssBtn');
if (!btn) return;
@@ -1525,18 +1587,28 @@ async function summarizeRss() {
try {
const result = await api.post('/api/rss/summarize', {});
// Create a new note with the summary
const newNote = await api.post('/api/notes', {
title: `📰 Résumé RSS - ${new Date().toLocaleDateString('fr-FR')}`,
content: result.summary
});
// Vérifier si les résumés sont activés
const settings = await api.get('/api/settings');
const summaryEnabled = settings.rss_summary_enabled === '1' || settings.rss_summary_enabled === 'true';
alert('Résumé généré et sauvegardé dans une nouvelle note !');
await loadNotes();
if (summaryEnabled) {
// Si les résumés sont activés, juste recharger le bloc RSS
alert('Résumé généré 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
});
// Expand the new note
state.expandedNoteId = newNote.id;
renderNotes();
alert('Résumé généré et sauvegardé dans une nouvelle note !');
await loadNotes();
// Expand the new note
state.expandedNoteId = newNote.id;
renderNotes();
}
} catch (error) {
console.error('Erreur génération résumé RSS:', error);
if (error.message && error.message.includes('400')) {