# app.py import streamlit as st from datetime import datetime from main import ( load_feeds, save_feeds, load_articles, save_articles, process_articles, rewrite_articles ) from utils.dalle_generator import generate_dalle_image import os from collections import Counter import time # === Page Configuration === st.set_page_config( page_title="📰 RSS Artikel Manager", layout="wide", initial_sidebar_state="collapsed" ) # === Custom CSS für modernes Design === st.markdown(""" """, unsafe_allow_html=True) # === Initialize Session State === if 'selected_articles' not in st.session_state: st.session_state.selected_articles = set() if 'search_query' not in st.session_state: st.session_state.search_query = "" if 'status_filter' not in st.session_state: st.session_state.status_filter = "New" if 'feed_filter' not in st.session_state: st.session_state.feed_filter = "Alle" # === Helper Functions === def get_status_badge(status): """Erstellt einen farbigen Status-Badge""" status_classes = { "New": "status-new", "Rewrite": "status-rewrite", "Process": "status-process", "Online": "status-online", "On Hold": "status-hold", "Trash": "status-trash" } class_name = status_classes.get(status, "status-new") return f'{status}' def format_date(date_str): """Formatiert Datum für bessere Lesbarkeit""" try: if "GMT" in date_str or "+" in date_str: return datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S %z").strftime("%d.%m.%Y %H:%M") else: return date_str[:16].replace("T", " ") except: return date_str[:10] def get_word_count(text): """Zählt Wörter im Text""" return len(text.split()) if text else 0 def show_notification(message, type="success"): """Zeigt eine Benachrichtigung an""" if type == "success": st.success(message) elif type == "error": st.error(message) elif type == "warning": st.warning(message) elif type == "info": st.info(message) # === Header === st.markdown("""

📰 RSS Artikel Manager

Moderne Verwaltung deiner RSS-Feeds und Artikel

""", unsafe_allow_html=True) # === Tab Navigation === tab1, tab2, tab3, tab4, tab5 = st.tabs([ "📋 Dashboard", "📰 Artikel", "📡 Feeds", "🖼️ Bilder", "📊 Statistiken" ]) # === Dashboard Tab === with tab1: st.header("📊 Übersicht") # Lade Daten all_articles = load_articles() feeds = load_feeds() # Statistiken col1, col2, col3, col4 = st.columns(4) with col1: st.markdown("""
{}
Gesamt Artikel
""".format(len(all_articles)), unsafe_allow_html=True) with col2: new_count = len([a for a in all_articles if a.get("status") == "New"]) st.markdown("""
{}
Neue Artikel
""".format(new_count), unsafe_allow_html=True) with col3: st.markdown("""
{}
RSS Feeds
""".format(len(feeds)), unsafe_allow_html=True) with col4: online_count = len([a for a in all_articles if a.get("status") == "Online"]) st.markdown("""
{}
Online
""".format(online_count), unsafe_allow_html=True) st.markdown("
", unsafe_allow_html=True) # Quick Actions st.subheader("⚡ Schnellaktionen") col1, col2, col3 = st.columns(3) with col1: if st.button("🔄 Alle Feeds aktualisieren", use_container_width=True): with st.spinner("Feeds werden aktualisiert..."): existing_ids = [a["id"] for a in all_articles] process_articles(existing_ids) show_notification("Feeds erfolgreich aktualisiert!") time.sleep(1) st.rerun() with col2: if st.button("✍️ Artikel umschreiben", use_container_width=True): rewrite_count = len([a for a in all_articles if a.get("status") == "Rewrite"]) if rewrite_count > 0: with st.spinner(f"{rewrite_count} Artikel werden umgeschrieben..."): rewrite_articles() show_notification(f"{rewrite_count} Artikel erfolgreich umgeschrieben!") time.sleep(1) st.rerun() else: show_notification("Keine Artikel zum Umschreiben gefunden.", "info") with col3: if st.button("🧹 Aufräumen", use_container_width=True): trash_count = len([a for a in all_articles if a.get("status") == "Trash"]) if trash_count > 0: show_notification(f"{trash_count} Artikel im Papierkorb gefunden.", "info") else: show_notification("Keine Artikel zum Aufräumen gefunden.", "info") # Neueste Artikel Preview st.subheader("🕒 Neueste Artikel") recent_articles = sorted(all_articles, key=lambda x: x.get("date", ""), reverse=True)[:5] for article in recent_articles: st.markdown(f"""
{article.get('title', 'Kein Titel')}
{format_date(article.get('date', ''))}
{get_status_badge(article.get('status', 'New'))}
""", unsafe_allow_html=True) # === Artikel Tab === with tab2: st.header("📰 Artikel verwalten") # Filter Section st.markdown('
', unsafe_allow_html=True) st.subheader("🔍 Filter & Suche") col1, col2, col3 = st.columns(3) with col1: status_options = ["Alle", "New", "Rewrite", "Process", "Online", "On Hold", "Trash"] st.session_state.status_filter = st.selectbox( "Status", status_options, index=status_options.index(st.session_state.status_filter) ) with col2: # Feed Filter source_to_name = {f.get("url"): f.get("name", "Unbekannt") for f in feeds} source_counter = Counter([a.get("source", "Unbekannt") for a in all_articles]) feed_options = ["Alle"] feed_map = {"Alle": None} for source, count in source_counter.items(): name = source_to_name.get(source, "Unbekannt") label = f"{name} ({count})" feed_options.append(label) feed_map[label] = source selected_feed_label = st.selectbox("Feed", feed_options) st.session_state.feed_filter = selected_feed_label with col3: st.session_state.search_query = st.text_input( "Suche", value=st.session_state.search_query, placeholder="Titel, Text oder Tags durchsuchen..." ) st.markdown('
', unsafe_allow_html=True) # Filter anwenden filtered_articles = all_articles # Status Filter if st.session_state.status_filter != "Alle": filtered_articles = [a for a in filtered_articles if a.get("status") == st.session_state.status_filter] # Feed Filter if st.session_state.feed_filter != "Alle": selected_source = feed_map[st.session_state.feed_filter] filtered_articles = [a for a in filtered_articles if a.get("source") == selected_source] # Suche if st.session_state.search_query: query = st.session_state.search_query.lower() filtered_articles = [ a for a in filtered_articles if query in a.get("title", "").lower() or query in a.get("text", "").lower() or any(query in tag.lower() for tag in a.get("tags", [])) ] # Ergebnisse anzeigen st.write(f"**{len(filtered_articles)} Artikel gefunden**") # Artikel Cards for article in filtered_articles: has_incomplete_images = any( not all(k in img and img[k] for k in ("caption", "copyright", "copyright_url")) for img in article.get("images", []) ) # Article Card st.markdown('
', unsafe_allow_html=True) # Header col1, col2 = st.columns([3, 1]) with col1: title = article.get("title", "Kein Titel") if has_incomplete_images: title += " ⚠️" st.markdown(f"**{title}**") st.markdown(f"📅 {format_date(article.get('date', ''))}") with col2: st.markdown(get_status_badge(article.get("status", "New")), unsafe_allow_html=True) # Content Preview summary = article.get("summary", "")[:200] if len(summary) == 200: summary += "..." st.markdown(summary) # Meta Info col1, col2, col3 = st.columns(3) with col1: st.markdown(f"📝 **{get_word_count(article.get('text', ''))} Wörter**") with col2: tags = article.get("tags", []) if tags: st.markdown(f"🏷️ {', '.join(tags[:3])}{'...' if len(tags) > 3 else ''}") with col3: source_name = source_to_name.get(article.get("source", ""), "Unbekannt") st.markdown(f"📡 {source_name}") # Actions col1, col2, col3, col4 = st.columns(4) with col1: # Status ändern status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"] current_status = article.get("status", "New") new_status = st.selectbox( "Status", status_options, index=status_options.index(current_status), key=f"status_{article['id']}" ) if new_status != current_status: # Artikel in der Liste finden und aktualisieren for idx, art in enumerate(all_articles): if art["id"] == article["id"]: all_articles[idx]["status"] = new_status break save_articles(all_articles) show_notification(f"Status auf '{new_status}' geändert!") time.sleep(0.5) st.rerun() with col2: if st.button("📋 Text kopieren", key=f"copy_{article['id']}"): text_to_copy = f"{article['title']}\n\n{article['text']}\n\nQuelle: {article['link']}" st.code(text_to_copy, language="markdown") show_notification("Text bereit zum Kopieren!") with col3: if st.button("🔗 Original öffnen", key=f"link_{article['id']}"): st.markdown(f"[🔗 Artikel öffnen]({article.get('link', '#')})") with col4: # Details anzeigen if st.button("📖 Details", key=f"details_{article['id']}"): st.session_state[f"show_details_{article['id']}"] = not st.session_state.get(f"show_details_{article['id']}", False) # Details Section (wenn erweitert) if st.session_state.get(f"show_details_{article['id']}", False): st.markdown("---") # Artikel Text with st.expander("📝 Volltext", expanded=False): st.code(article.get("text", ""), language="markdown") # Tags bearbeiten with st.expander("🏷️ Tags bearbeiten", expanded=False): current_tags = ", ".join(article.get("tags", [])) new_tags = st.text_area("Tags (getrennt durch Komma)", value=current_tags, key=f"tags_{article['id']}") if st.button("Tags speichern", key=f"save_tags_{article['id']}"): tag_list = [tag.strip() for tag in new_tags.split(",") if tag.strip()] for idx, art in enumerate(all_articles): if art["id"] == article["id"]: all_articles[idx]["tags"] = tag_list break save_articles(all_articles) show_notification("Tags gespeichert!") st.rerun() # Bilder if article.get("images"): with st.expander("🖼️ Bilder verwalten", expanded=False): for i, img in enumerate(article.get("images", [])): col1, col2 = st.columns([1, 2]) with col1: st.image(img["url"], width=200) with col2: caption = st.text_input("Bildtitel", value=img.get("caption", ""), key=f"caption_{article['id']}_{i}") copyright_text = st.text_input("Copyright", value=img.get("copyright", ""), key=f"copyright_{article['id']}_{i}") copyright_url = st.text_input("Quelle URL", value=img.get("copyright_url", ""), key=f"copyright_url_{article['id']}_{i}") if st.button("Bilddaten speichern", key=f"save_img_{article['id']}_{i}"): img["caption"] = caption or "Kein Bildtitel vorhanden" img["copyright"] = copyright_text or "Unbekannt" img["copyright_url"] = copyright_url or "#" for idx, art in enumerate(all_articles): if art["id"] == article["id"]: all_articles[idx] = article break save_articles(all_articles) show_notification("Bilddaten gespeichert!") st.rerun() # DALL-E Bildgenerierung if st.button("🪄 KI-Bild generieren", key=f"dalle_{article['id']}"): if not any(img.get("copyright") == "OpenAI DALL·E" for img in article.get("images", [])): with st.spinner("Bild wird generiert..."): prompt = article["title"] image_url = generate_dalle_image(prompt) if image_url: article.setdefault("images", []).append({ "url": image_url, "alt": f"KI-generiertes Titelbild zu: {prompt}", "caption": f"KI-generiertes Titelbild zu: {prompt}", "copyright": "OpenAI DALL·E", "copyright_url": "https://openai.com/dall-e" }) for idx, art in enumerate(all_articles): if art["id"] == article["id"]: all_articles[idx] = article break save_articles(all_articles) show_notification("DALL·E-Bild erfolgreich hinzugefügt!") st.rerun() else: show_notification("Fehler beim Erzeugen des Bildes.", "error") else: show_notification("Ein KI-generiertes Bild ist bereits vorhanden.", "info") st.markdown('
', unsafe_allow_html=True) # === Feeds Tab === with tab3: st.header("📡 RSS Feeds verwalten") # Feed hinzufügen with st.expander("➕ Neuen Feed hinzufügen", expanded=False): col1, col2 = st.columns(2) with col1: new_url = st.text_input("Feed URL") with col2: new_name = st.text_input("Feed Name") if st.button("Feed hinzufügen", use_container_width=True): if new_url and new_name: if not any(f.get("url") == new_url for f in feeds): feeds.append({"url": new_url, "name": new_name}) save_feeds(feeds) show_notification(f"Feed '{new_name}' hinzugefügt!") st.rerun() else: show_notification("Dieser Feed existiert bereits.", "warning") else: show_notification("Bitte URL und Name eingeben.", "error") # Feeds anzeigen for idx, feed in enumerate(feeds): feed_url = feed.get("url", "") feed_name = feed.get("name", "Unbekannt") article_count = sum(1 for a in all_articles if a.get("source") == feed_url) st.markdown(f"""
{feed_name}
{feed_url}
📰 {article_count} Artikel
{article_count} Artikel
""", unsafe_allow_html=True) # Feed Actions col1, col2, col3 = st.columns(3) with col1: if st.button("✏️ Bearbeiten", key=f"edit_feed_{idx}"): st.session_state[f"edit_feed_{idx}"] = not st.session_state.get(f"edit_feed_{idx}", False) with col2: if st.button("🔄 Aktualisieren", key=f"refresh_feed_{idx}"): with st.spinner("Feed wird aktualisiert..."): existing_ids = [a["id"] for a in all_articles] # Hier könntest du eine einzelne Feed-Update-Funktion implementieren process_articles(existing_ids) show_notification(f"Feed '{feed_name}' aktualisiert!") st.rerun() with col3: if st.button("🗑️ Löschen", key=f"delete_feed_{idx}"): feeds.pop(idx) save_feeds(feeds) show_notification(f"Feed '{feed_name}' gelöscht!", "warning") st.rerun() # Edit Form if st.session_state.get(f"edit_feed_{idx}", False): with st.form(f"edit_form_{idx}"): new_feed_url = st.text_input("URL", value=feed_url) new_feed_name = st.text_input("Name", value=feed_name) if st.form_submit_button("Änderungen speichern"): feeds[idx]["url"] = new_feed_url feeds[idx]["name"] = new_feed_name save_feeds(feeds) show_notification("Feed aktualisiert!") st.session_state[f"edit_feed_{idx}"] = False st.rerun() # === Bilder Tab === with tab4: st.header("🖼️ Bilderverwaltung") # Alle Bilder sammeln all_images = [] for article in all_articles: for img in article.get("images", []): img_data = img.copy() img_data["article_title"] = article.get("title", "Unbekannt") img_data["article_id"] = article.get("id") all_images.append(img_data) if all_images: st.write(f"**{len(all_images)} Bilder gefunden**") # Bilder in Spalten anzeigen cols = st.columns(3) for idx, img in enumerate(all_images): with cols[idx % 3]: st.image(img["url"], use_column_width=True) st.markdown(f"**{img.get('caption', 'Kein Titel')}**") st.markdown(f"📰 {img['article_title']}") st.markdown(f"©️ {img.get('copyright', 'Unbekannt')}") if img.get("copyright_url") and img["copyright_url"] != "#": st.markdown(f"[🔗 Quelle]({img['copyright_url']})") else: st.info("Keine Bilder gefunden.") # === Statistiken Tab === with tab5: st.header("📊 Detaillierte Statistiken") # Status Verteilung status_counts = Counter([a.get("status", "New") for a in all_articles]) col1, col2 = st.columns(2) with col1: st.subheader("📈 Status Verteilung") for status, count in status_counts.items(): percentage = (count / len(all_articles) * 100) if all_articles else 0 st.markdown(f"{get_status_badge(status)} {count} ({percentage:.1f}%)", unsafe_allow_html=True) with col2: st.subheader("📡 Artikel pro Feed") feed_counts = Counter([source_to_name.get(a.get("source", ""), "Unbekannt") for a in all_articles]) for feed_name, count in feed_counts.most_common(): st.markdown(f"**{feed_name}:** {count} Artikel") # Weitere Statistiken st.subheader("📝 Textstatistiken") word_counts = [get_word_count(a.get("text", "")) for a in all_articles] if word_counts: col1, col2, col3 = st.columns(3) with col1: st.metric("Durchschnittliche Wortanzahl", f"{sum(word_counts) // len(word_counts)}") with col2: st.metric("Längster Artikel", f"{max(word_counts)} Wörter") with col3: st.metric("Kürzester Artikel", f"{min(word_counts)} Wörter") # Tag Cloud Simulation st.subheader("🏷️ Häufigste Tags") all_tags = [] for article in all_articles: all_tags.extend(article.get("tags", [])) if all_tags: tag_counts = Counter(all_tags) for tag, count in tag_counts.most_common(10): st.markdown(f"**{tag}:** {count}x verwendet") else: st.info("Keine Tags gefunden.")