# 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, upload_articles_to_wp ) from utils.dalle_generator import generate_dalle_image from utils.wordpress_uploader import WordPressUploader from utils.css_loader import load_css, apply_dark_theme from utils.config import validate_env 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" ) # === CSS & Theme laden === load_css() apply_dark_theme() # === Environment-Validierung (.env) === env_check = validate_env() if not env_check.get("ok"): st.error("🔒 Sicherheits-/Konfigurationshinweis: Bitte .env korrekt konfigurieren.") for msg in env_check.get("errors", []): st.markdown(f"- ❌ {msg}") for msg in env_check.get("warnings", []): st.markdown(f"- ⚠️ {msg}") elif env_check.get("warnings"): st.info("ℹ️ Hinweise zur Konfiguration:") for msg in env_check.get("warnings", []): st.markdown(f"- ⚠️ {msg}") # === 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", "WordPress Pending": "status-wp-pending" } 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) def test_wordpress_connection(): """Testet die WordPress-Verbindung""" try: uploader = WordPressUploader() success, message = uploader.test_connection() return success, message except Exception as e: return False, f"Fehler beim Testen der Verbindung: {str(e)}" # === Header === st.markdown("""

📰 RSS Artikel Manager

Moderne Verwaltung deiner RSS-Feeds und Artikel mit WordPress-Integration

""", unsafe_allow_html=True) # === Tab Navigation === tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ "📋 Dashboard", "📰 Artikel", "📡 Feeds", "🖼️ Bilder", "📊 Statistiken", "🔧 WordPress" ]) # === Dashboard Tab === with tab1: st.header("📊 Übersicht") # Lade Daten all_articles = load_articles() feeds = load_feeds() # Statistiken col1, col2, col3, col4, col5 = st.columns(5) 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: process_count = len([a for a in all_articles if a.get("status") == "Process"]) st.markdown("""
{}
Bereit für WP
""".format(process_count), unsafe_allow_html=True) with col4: wp_pending_count = len([a for a in all_articles if a.get("status") == "WordPress Pending"]) st.markdown("""
{}
WP Ausstehend
""".format(wp_pending_count), unsafe_allow_html=True) with col5: 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, col4 = st.columns(4) 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("📤 WordPress Upload", use_container_width=True): process_count = len([a for a in all_articles if a.get("status") == "Process"]) if process_count > 0: with st.spinner(f"{process_count} Artikel werden zu WordPress hochgeladen..."): upload_results = upload_articles_to_wp() if upload_results.get('error'): show_notification(f"Fehler beim WordPress-Upload: {upload_results['error']}", "error") else: successful = upload_results.get('successful', 0) failed = upload_results.get('failed', 0) duplicates = upload_results.get('duplicates', 0) if successful > 0: show_notification(f"✅ {successful} Artikel erfolgreich zu WordPress hochgeladen!") if failed > 0: show_notification(f"⚠️ {failed} Artikel konnten nicht hochgeladen werden.", "warning") if duplicates > 0: show_notification(f"ℹ️ {duplicates} Duplikate übersprungen.", "info") time.sleep(2) st.rerun() else: show_notification("Keine Artikel für WordPress-Upload gefunden.", "info") with col4: 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") # WordPress-Status-Übersicht if wp_pending_count > 0 or online_count > 0: st.subheader("🔗 WordPress-Status") wp_articles = [a for a in all_articles if a.get("status") in ["WordPress Pending", "Online"]] for article in wp_articles[:5]: # Nur die ersten 5 anzeigen st.markdown(f"""
{article.get('title', 'Kein Titel')} {get_status_badge(article.get('status', 'Unknown'))}
WP Post ID: {article.get('wp_post_id', 'Unbekannt')} | Upload: {format_date(article.get('wp_upload_date', ''))}
""", unsafe_allow_html=True) # 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')}

{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", "WordPress Pending"] 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 und Massenoperationen col1, col2 = st.columns([2, 1]) with col1: st.write(f"**{len(filtered_articles)} Artikel gefunden**") with col2: # Select All / None Buttons if filtered_articles: col_select_1, col_select_2 = st.columns(2) with col_select_1: if st.button("✓ Alle auswählen", key="select_all"): for article in filtered_articles: st.session_state.selected_articles.add(article['id']) st.rerun() with col_select_2: if st.button("✗ Auswahl aufheben", key="select_none"): st.session_state.selected_articles.clear() st.rerun() # Bulk Operations Section selected_count = len(st.session_state.selected_articles) if selected_count > 0: st.markdown(f"""

⚡ Massenoperationen ({selected_count} Artikel ausgewählt)

""", unsafe_allow_html=True) # Quick Actions für ausgewählte Artikel col1, col2, col3, col4, col5 = st.columns(5) with col1: if st.button("🔄 Feeds aktualisieren", use_container_width=True, key="bulk_update_feeds"): 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: # Bulk Status Change bulk_status = st.selectbox( "Status ändern", ["--Auswählen--"] + ["New", "Rewrite", "Process", "Online", "On Hold", "Trash", "WordPress Pending"], key="bulk_status" ) if bulk_status != "--Auswählen--" and st.button("Status anwenden", key="apply_bulk_status"): changed_count = 0 for article in all_articles: if article["id"] in st.session_state.selected_articles: article["status"] = bulk_status changed_count += 1 if changed_count > 0: save_articles(all_articles) show_notification(f"{changed_count} Artikel auf '{bulk_status}' gesetzt!") st.session_state.selected_articles.clear() st.rerun() with col3: # Bulk Rewrite rewrite_selected_count = len([a for a in all_articles if a["id"] in st.session_state.selected_articles and a.get("status") != "Rewrite"]) if st.button(f"✍️ Artikel umschreiben ({rewrite_selected_count})", use_container_width=True, key="bulk_rewrite"): # Ausgewählte Artikel auf "Rewrite" setzen for article in all_articles: if article["id"] in st.session_state.selected_articles: article["status"] = "Rewrite" save_articles(all_articles) # Umschreiben starten with st.spinner(f"{rewrite_selected_count} Artikel werden umgeschrieben..."): rewrite_articles() show_notification(f"{rewrite_selected_count} Artikel erfolgreich umgeschrieben!") st.session_state.selected_articles.clear() time.sleep(1) st.rerun() with col4: # Bulk WordPress Upload wp_ready_selected = len([a for a in all_articles if a["id"] in st.session_state.selected_articles and a.get("status") == "Process"]) if wp_ready_selected > 0: if st.button(f"📤 WordPress Upload ({wp_ready_selected})", use_container_width=True, key="bulk_wp_upload"): with st.spinner(f"{wp_ready_selected} Artikel werden zu WordPress hochgeladen..."): # Nur die ausgewählten "Process" Artikel hochladen selected_process_articles = [a for a in all_articles if a["id"] in st.session_state.selected_articles and a.get("status") == "Process"] if selected_process_articles: from utils.wordpress_uploader import upload_articles_to_wordpress upload_results = upload_articles_to_wordpress(selected_process_articles) if upload_results.get('error'): show_notification(f"Fehler beim WordPress-Upload: {upload_results['error']}", "error") else: successful = upload_results.get('successful', 0) failed = upload_results.get('failed', 0) duplicates = upload_results.get('duplicates', 0) # Status der erfolgreich hochgeladenen Artikel ändern if successful > 0: for detail in upload_results.get('details', []): if detail.get('success'): article_id = detail.get('article_id') for article in all_articles: if article.get('id') == article_id: article['status'] = "WordPress Pending" article['wp_upload_date'] = datetime.now().isoformat() article['wp_post_id'] = detail.get('wp_post_id') break save_articles(all_articles) if successful > 0: show_notification(f"✅ {successful} Artikel erfolgreich zu WordPress hochgeladen!") if failed > 0: show_notification(f"⚠️ {failed} Artikel konnten nicht hochgeladen werden.", "warning") if duplicates > 0: show_notification(f"ℹ️ {duplicates} Duplikate übersprungen.", "info") st.session_state.selected_articles.clear() time.sleep(2) st.rerun() else: st.markdown("*Keine Process-Artikel ausgewählt*") with col5: # Bulk Delete/Trash if st.button("🗑️ In Papierkorb", use_container_width=True, key="bulk_trash"): trash_count = 0 for article in all_articles: if article["id"] in st.session_state.selected_articles: article["status"] = "Trash" trash_count += 1 if trash_count > 0: save_articles(all_articles) show_notification(f"{trash_count} Artikel in Papierkorb verschoben!") st.session_state.selected_articles.clear() st.rerun() # 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 with Checkbox col_check, col_content, col_status = st.columns([0.3, 2.7, 1]) with col_check: # Checkbox für Artikel-Auswahl is_selected = article["id"] in st.session_state.selected_articles if st.checkbox("", value=is_selected, key=f"check_{article['id']}"): st.session_state.selected_articles.add(article['id']) else: st.session_state.selected_articles.discard(article['id']) with col_content: title = article.get("title", "Kein Titel") if has_incomplete_images: title += " ⚠️" st.markdown(f'

{title}

', unsafe_allow_html=True) st.markdown(f'
📅 {format_date(article.get("date", ""))}
', unsafe_allow_html=True) # WordPress-Info anzeigen falls vorhanden if article.get("wp_post_id"): st.markdown(f'
🔗 WordPress ID: {article.get("wp_post_id")} | Upload: {format_date(article.get("wp_upload_date", ""))}
', unsafe_allow_html=True) with col_status: 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(f'
{summary}
', unsafe_allow_html=True) # Meta Info col1, col2, col3 = st.columns(3) with col1: st.markdown(f'
📝 **{get_word_count(article.get("text", ""))} Wörter**
', unsafe_allow_html=True) with col2: tags = article.get("tags", []) if tags: st.markdown(f'
🏷️ {", ".join(tags[:3])}{"..." if len(tags) > 3 else ""}
', unsafe_allow_html=True) with col3: source_name = source_to_name.get(article.get("source", ""), "Unbekannt") st.markdown(f'
📡 {source_name}
', unsafe_allow_html=True) # Actions col1, col2, col3, col4, col5 = st.columns(5) with col1: # Status ändern status_options = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash", "WordPress Pending"] 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: # WordPress Upload Button für einzelne Artikel if article.get("status") == "Process": if st.button("📤 WordPress", key=f"wp_upload_{article['id']}"): with st.spinner("Lade zu WordPress hoch..."): from utils.wordpress_uploader import upload_single_article_to_wordpress success, message, wp_post_id = upload_single_article_to_wordpress(article) if success: # Status ändern for idx, art in enumerate(all_articles): if art["id"] == article["id"]: all_articles[idx]["status"] = "WordPress Pending" all_articles[idx]["wp_upload_date"] = datetime.now().isoformat() all_articles[idx]["wp_post_id"] = wp_post_id break save_articles(all_articles) show_notification("✅ Erfolgreich zu WordPress hochgeladen!") else: show_notification(f"❌ WordPress-Upload fehlgeschlagen: {message}", "error") time.sleep(1) st.rerun() with col5: # 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}

{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.markdown('
', unsafe_allow_html=True) st.image(img["url"], use_column_width=True) st.markdown(f'{img.get("caption", "Kein Titel")}', unsafe_allow_html=True) st.markdown(f'
📰 {img["article_title"]}
', unsafe_allow_html=True) st.markdown(f'
©️ {img.get("copyright", "Unbekannt")}
', unsafe_allow_html=True) if img.get("copyright_url") and img["copyright_url"] != "#": st.markdown(f'🔗 Quelle', unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) 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
', unsafe_allow_html=True) # WordPress-Statistiken st.subheader("🔗 WordPress-Statistiken") wp_articles = [a for a in all_articles if a.get("wp_post_id")] if wp_articles: col1, col2, col3 = st.columns(3) with col1: st.markdown("""
{}
WordPress Artikel
""".format(len(wp_articles)), unsafe_allow_html=True) with col2: pending_count = len([a for a in wp_articles if a.get("status") == "WordPress Pending"]) st.markdown("""
{}
Ausstehend
""".format(pending_count), unsafe_allow_html=True) with col3: online_wp_count = len([a for a in wp_articles if a.get("status") == "Online"]) st.markdown("""
{}
Online
""".format(online_wp_count), unsafe_allow_html=True) # Neueste WordPress-Uploads recent_wp = sorted([a for a in wp_articles if a.get("wp_upload_date")], key=lambda x: x.get("wp_upload_date", ""), reverse=True)[:5] if recent_wp: st.subheader("🕒 Neueste WordPress-Uploads") for article in recent_wp: st.markdown(f"""

{article.get('title', 'Kein Titel')}

{get_status_badge(article.get('status', 'Unknown'))}
WP ID: {article.get('wp_post_id')} | Upload: {format_date(article.get('wp_upload_date', ''))}
""", unsafe_allow_html=True) else: st.info("Noch keine Artikel zu WordPress hochgeladen.") # 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.markdown("""
{}
Durchschnittliche Wortanzahl
""".format(sum(word_counts) // len(word_counts)), unsafe_allow_html=True) with col2: st.markdown("""
{}
Längster Artikel (Wörter)
""".format(max(word_counts)), unsafe_allow_html=True) with col3: st.markdown("""
{}
Kürzester Artikel (Wörter)
""".format(min(word_counts)), unsafe_allow_html=True) # 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
', unsafe_allow_html=True) else: st.info("Keine Tags gefunden.") # === WordPress Tab === with tab6: st.header("🔧 WordPress-Integration") # Verbindungstest st.subheader("🔗 Verbindungstest") col1, col2 = st.columns(2) with col1: if st.button("🧪 WordPress-Verbindung testen", use_container_width=True): with st.spinner("Teste Verbindung..."): success, message = test_wordpress_connection() if success: show_notification(f"✅ {message}") else: show_notification(f"❌ {message}", "error") with col2: # WordPress-Konfiguration anzeigen wp_url = os.getenv("WP_BASE_URL", "Nicht konfiguriert") wp_user = os.getenv("WP_USERNAME", "Nicht konfiguriert") wp_base64 = os.getenv("WP_AUTH_BASE64", "") st.markdown(f"""
WordPress-Konfiguration:
URL: {wp_url}
Benutzer: {wp_user}
Passwort: {'✅ Konfiguriert' if os.getenv("WP_PASSWORD") else '❌ Nicht konfiguriert'}
Base64 Auth: {'✅ Konfiguriert' if wp_base64 else '❌ Nicht konfiguriert'}
""", unsafe_allow_html=True) # Sicherheit: Kein Anzeigen sensibler Auth-Details mehr # Bulk Upload st.subheader("📦 Massenupload") process_articles_list = [a for a in all_articles if a.get("status") == "Process"] if process_articles_list: st.write(f"**{len(process_articles_list)} Artikel bereit für WordPress-Upload:**") # Artikel-Vorschau for article in process_articles_list[:5]: # Nur die ersten 5 anzeigen st.markdown(f'
{article.get("title", "Kein Titel")} ({get_word_count(article.get("text", ""))} Wörter)
', unsafe_allow_html=True) if len(process_articles_list) > 5: st.markdown(f'
... und {len(process_articles_list) - 5} weitere
', unsafe_allow_html=True) col1, col2 = st.columns(2) with col1: if st.button("📤 Alle zu WordPress hochladen", use_container_width=True): with st.spinner(f"Lade {len(process_articles_list)} Artikel zu WordPress hoch..."): upload_results = upload_articles_to_wp() # Detaillierte Ergebnisse anzeigen st.subheader("📊 Upload-Ergebnisse") if upload_results.get('error'): show_notification(f"❌ Fehler: {upload_results['error']}", "error") else: col1, col2, col3 = st.columns(3) with col1: st.markdown("""
{}
Erfolgreich
""".format(upload_results.get('successful', 0)), unsafe_allow_html=True) with col2: st.markdown("""
{}
Fehlgeschlagen
""".format(upload_results.get('failed', 0)), unsafe_allow_html=True) with col3: st.markdown("""
{}
Duplikate
""".format(upload_results.get('duplicates', 0)), unsafe_allow_html=True) # Details anzeigen if upload_results.get('details'): st.subheader("📋 Upload-Details") for detail in upload_results['details']: status_icon = "✅" if detail['success'] else "❌" st.markdown(f'
{status_icon} {detail["title"]}: {detail["message"]}
', unsafe_allow_html=True) time.sleep(2) st.rerun() with col2: st.markdown("""
💡 Info:
Artikel erhalten den Status 'WordPress Pending' nach erfolgreichem Upload.
""", unsafe_allow_html=True) else: st.info("Keine Artikel mit Status 'Process' gefunden. Artikel müssen zuerst umgeschrieben werden.") # WordPress-Artikel-Übersicht st.subheader("📋 WordPress-Artikel-Übersicht") wp_articles = [a for a in all_articles if a.get("wp_post_id")] if wp_articles: # Filter für WordPress-Artikel wp_status_filter = st.selectbox( "WordPress-Status filtern", ["Alle", "WordPress Pending", "Online"], key="wp_status_filter" ) filtered_wp_articles = wp_articles if wp_status_filter != "Alle": filtered_wp_articles = [a for a in wp_articles if a.get("status") == wp_status_filter] st.write(f"**{len(filtered_wp_articles)} WordPress-Artikel gefunden**") # WordPress-Artikel anzeigen for article in filtered_wp_articles: st.markdown(f"""
{article.get('title', 'Kein Titel')}
WP ID: {article.get('wp_post_id')} | Upload: {format_date(article.get('wp_upload_date', ''))}
{get_status_badge(article.get('status', 'Unknown'))}
""", unsafe_allow_html=True) else: st.info("Noch keine Artikel zu WordPress hochgeladen.") # Konfigurationshilfe st.subheader("⚙️ Konfiguration") with st.expander("📋 .env-Datei Vorlage", expanded=False): st.code(""" # WordPress-Konfiguration WP_BASE_URL=https://your-site.tld # Entweder Base64 (empfohlen) ODER Benutzername/Passwort (Application Password) WP_AUTH_BASE64= # Oder alternativ: WP_USERNAME= WP_PASSWORD= # OpenAI-Konfiguration (optional für Umschreibung) OPENAI_API_KEY= """, language="bash") with st.expander("🔑 Base64-Authentifizierung verstehen", expanded=False): st.markdown("""

WordPress REST API Authentifizierung:

Die WordPress REST API nutzt Basic-Auth mit Base64-kodierten Zugangsdaten:
Authorization: Basic <base64(username:password)>

Empfehlung: In der .env WP_AUTH_BASE64 setzen (aus username:application_password erzeugt).
Alternativ können WP_USERNAME und WP_PASSWORD gesetzt werden; dann wird Base64 zur Laufzeit generiert.
""", unsafe_allow_html=True) with st.expander("📖 WordPress-API Berechtigungen", expanded=False): st.markdown("""

Erforderliche Berechtigungen für den WordPress-Benutzer:

edit_posts - Beiträge erstellen und bearbeiten
publish_posts - Beiträge veröffentlichen (für Status-Änderungen)
upload_files - Dateien hochladen (für spätere Bild-Uploads)
edit_categories - Kategorien verwalten
edit_tags - Tags verwalten

Anwendungspasswort erstellen:
1. WordPress Admin → Benutzer → Profil
2. Unter "Anwendungspasswörter" neues Passwort erstellen
3. Name: "RSS Feed Manager"
4. Generiertes Passwort in .env-Datei eintragen
""", unsafe_allow_html=True)