FirstSync
This commit is contained in:
commit
633f312bc7
4 changed files with 306 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.env
|
||||||
190
app.py
Normal file
190
app.py
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import streamlit as st
|
||||||
|
import feedparser
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import pandas as pd
|
||||||
|
import openai
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# ==== Konfiguration ====
|
||||||
|
ARTICLES_FILE = "articles.json"
|
||||||
|
FEEDS_FILE = "feeds.json"
|
||||||
|
DEFAULT_STATUS = "New"
|
||||||
|
ALL_STATUSES = ["New", "Rewrite", "Process", "Online", "On Hold", "Trash"]
|
||||||
|
|
||||||
|
# ==== API Schlüssel laden ====
|
||||||
|
load_dotenv()
|
||||||
|
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
# ==== Hilfsfunktionen ====
|
||||||
|
def load_articles():
|
||||||
|
if not os.path.exists(ARTICLES_FILE):
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
with open(ARTICLES_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_articles(articles):
|
||||||
|
with open(ARTICLES_FILE, "w") as f:
|
||||||
|
json.dump(articles, f, indent=2)
|
||||||
|
|
||||||
|
def load_feeds():
|
||||||
|
if not os.path.exists(FEEDS_FILE):
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
with open(FEEDS_FILE, "r") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def save_feeds(feeds):
|
||||||
|
with open(FEEDS_FILE, "w") as f:
|
||||||
|
json.dump(feeds, f, indent=2)
|
||||||
|
|
||||||
|
def fetch_articles_from_feeds(feeds):
|
||||||
|
new_articles = []
|
||||||
|
existing_links = {a['link'] for a in load_articles()}
|
||||||
|
for feed_url in feeds:
|
||||||
|
parsed = feedparser.parse(feed_url)
|
||||||
|
for entry in parsed.entries:
|
||||||
|
if entry.link in existing_links:
|
||||||
|
continue
|
||||||
|
content = ""
|
||||||
|
if 'content' in entry:
|
||||||
|
content = entry.content[0].value
|
||||||
|
elif 'summary' in entry:
|
||||||
|
content = entry.summary
|
||||||
|
article = {
|
||||||
|
"id": str(uuid.uuid4()),
|
||||||
|
"date": entry.get("published", datetime.now().isoformat()),
|
||||||
|
"title": entry.get("title", "(kein Titel)"),
|
||||||
|
"summary": content[:150],
|
||||||
|
"content": content,
|
||||||
|
"word_count": len(content.split()),
|
||||||
|
"tags": [],
|
||||||
|
"status": DEFAULT_STATUS,
|
||||||
|
"link": entry.link
|
||||||
|
}
|
||||||
|
new_articles.append(article)
|
||||||
|
return new_articles
|
||||||
|
|
||||||
|
def format_date(date_str):
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(date_str).strftime("%d.%m.%y")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
return datetime.strptime(date_str[:25], "%a, %d %b %Y %H:%M:%S").strftime("%d.%m.%y")
|
||||||
|
except Exception:
|
||||||
|
return date_str
|
||||||
|
|
||||||
|
def rewrite_article_with_gpt(original_text):
|
||||||
|
prompt = (
|
||||||
|
"Schreibe folgenden Artikel um und formuliere ihn in journalistischem Stil neu. "
|
||||||
|
"Füge am Ende eine Liste von 2–3 passenden Tags hinzu (nur Schlagwörter, keine Hashtags):\n"
|
||||||
|
f"{original_text}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
response = openai.ChatCompletion.create(
|
||||||
|
model="gpt-4",
|
||||||
|
messages=[{"role": "user", "content": prompt}],
|
||||||
|
temperature=0.7
|
||||||
|
)
|
||||||
|
return response.choices[0].message.content
|
||||||
|
except Exception as e:
|
||||||
|
return f"FEHLER: {e}"
|
||||||
|
|
||||||
|
# ==== UI ====
|
||||||
|
st.set_page_config(page_title="RSS Artikel Manager", layout="wide")
|
||||||
|
st.title("📰 RSS Artikel Manager")
|
||||||
|
|
||||||
|
# Bereich: Feed-Verwaltung
|
||||||
|
st.sidebar.header("RSS Feeds verwalten")
|
||||||
|
feeds = load_feeds()
|
||||||
|
new_feed = st.sidebar.text_input("Neuen Feed hinzufügen")
|
||||||
|
if st.sidebar.button("➕ Feed hinzufügen") and new_feed:
|
||||||
|
feeds.append(new_feed)
|
||||||
|
save_feeds(feeds)
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
if feeds:
|
||||||
|
remove_feed = st.sidebar.selectbox("Feed entfernen", [""] + feeds)
|
||||||
|
if st.sidebar.button("🗑️ Entfernen") and remove_feed:
|
||||||
|
feeds.remove(remove_feed)
|
||||||
|
save_feeds(feeds)
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.sidebar.info("Noch keine Feeds hinzugefügt")
|
||||||
|
|
||||||
|
# Bereich: Artikel laden
|
||||||
|
if st.button("🔄 Artikel aus Feeds laden"):
|
||||||
|
new = fetch_articles_from_feeds(feeds)
|
||||||
|
if new:
|
||||||
|
all_articles = load_articles() + new
|
||||||
|
save_articles(all_articles)
|
||||||
|
st.success(f"{len(new)} neue Artikel geladen.")
|
||||||
|
else:
|
||||||
|
st.info("Keine neuen Artikel gefunden.")
|
||||||
|
|
||||||
|
# Button zum Umschreiben aller Artikel mit Status "Rewrite"
|
||||||
|
rewrite_articles = [a for a in load_articles() if a["status"] == "Rewrite"]
|
||||||
|
if rewrite_articles:
|
||||||
|
if st.button("✍️ Alle Artikel mit Status 'Rewrite' umschreiben"):
|
||||||
|
all_articles = load_articles()
|
||||||
|
with st.spinner("Artikel werden umgeschrieben..."):
|
||||||
|
for a in all_articles:
|
||||||
|
if a["status"] == "Rewrite":
|
||||||
|
result = rewrite_article_with_gpt(a["content"])
|
||||||
|
if "FEHLER:" not in result:
|
||||||
|
# Aufteilen in Text und Tags, falls möglich
|
||||||
|
if "Tags:" in result:
|
||||||
|
rewritten, tags = result.rsplit("Tags:", 1)
|
||||||
|
a["content"] = rewritten.strip()
|
||||||
|
a["tags"] = [t.strip() for t in tags.split(",")][:3]
|
||||||
|
else:
|
||||||
|
a["content"] = result.strip()
|
||||||
|
a["summary"] = a["content"][:150]
|
||||||
|
a["word_count"] = len(a["content"].split())
|
||||||
|
a["status"] = "Process"
|
||||||
|
save_articles(all_articles)
|
||||||
|
st.success("Artikel erfolgreich umgeschrieben und aktualisiert.")
|
||||||
|
st.rerun()
|
||||||
|
|
||||||
|
# Bereich: Artikeltabelle
|
||||||
|
status_filter = st.selectbox("Status filtern", ALL_STATUSES, index=ALL_STATUSES.index(DEFAULT_STATUS))
|
||||||
|
articles = [a for a in load_articles() if a["status"] == status_filter]
|
||||||
|
|
||||||
|
if articles:
|
||||||
|
st.markdown("---")
|
||||||
|
st.subheader(f"Artikel mit Status '{status_filter}'")
|
||||||
|
|
||||||
|
# Checkbox-Auswahl manuell verwalten
|
||||||
|
selected_ids = []
|
||||||
|
for i, article in enumerate(articles):
|
||||||
|
cols = st.columns([0.5, 1.5, 3, 4, 1, 2, 1])
|
||||||
|
with cols[0]:
|
||||||
|
if st.checkbox("", key=article["id"]):
|
||||||
|
selected_ids.append(article["id"])
|
||||||
|
cols[1].markdown(format_date(article["date"]))
|
||||||
|
cols[2].markdown(f"**{article['title']}**")
|
||||||
|
cols[3].markdown(article["summary"])
|
||||||
|
cols[4].markdown(str(article["word_count"]))
|
||||||
|
cols[5].markdown(", ".join(article["tags"]) if article["tags"] else "")
|
||||||
|
cols[6].markdown(article["status"])
|
||||||
|
|
||||||
|
st.markdown("---")
|
||||||
|
if selected_ids:
|
||||||
|
new_status = st.selectbox("Neuen Status setzen für ausgewählte Artikel", ALL_STATUSES)
|
||||||
|
if st.button("✅ Status ändern"):
|
||||||
|
all_articles = load_articles()
|
||||||
|
for a in all_articles:
|
||||||
|
if a["id"] in selected_ids:
|
||||||
|
a["status"] = new_status
|
||||||
|
save_articles(all_articles)
|
||||||
|
st.success("Status aktualisiert.")
|
||||||
|
st.rerun()
|
||||||
|
else:
|
||||||
|
st.warning(f"Keine Artikel mit Status '{status_filter}' vorhanden.")
|
||||||
112
articles.json
Normal file
112
articles.json
Normal file
File diff suppressed because one or more lines are too long
3
feeds.json
Normal file
3
feeds.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
"https://www.camping-news.de/rss/"
|
||||||
|
]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue