e08c484838
Six-step weekly workflow (research → sources → storyline → draft → quality → publication) supporting Claude, ChatGPT, Gemini, and Mistral in parallel for creative steps. Web search via Anthropic tool for news research. Episode index built from 34 existing KNIEPUNKT episodes for redundancy checks. Sessions persisted as JSON for mid-workflow resume. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
203 lines
7.9 KiB
Python
203 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
"""KNIEPUNKT Assistant — weekly editorial workflow for Dr. André Knie."""
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.prompt import Confirm
|
|
|
|
from kniepunkt.llm import get_client
|
|
from kniepunkt.providers import get_configured
|
|
from kniepunkt import episodes as ep_module
|
|
from kniepunkt import session as sess_module
|
|
from kniepunkt import research, storyline, drafting, quality, publication
|
|
|
|
console = Console()
|
|
|
|
|
|
def _header():
|
|
console.print(Panel(
|
|
"[bold cyan]KNIEPUNKT Assistant[/bold cyan]\n"
|
|
"[dim]Redaktioneller Workflow · Dr. André Knie[/dim]",
|
|
border_style="cyan",
|
|
))
|
|
|
|
|
|
def _build_or_load_index(client) -> list[dict]:
|
|
import json
|
|
from pathlib import Path
|
|
|
|
cache = Path("episodes_cache.json")
|
|
if cache.exists():
|
|
rebuild = Confirm.ask("\n[dim]Episode-Index vorhanden. Neu aufbauen?[/dim]", default=False)
|
|
if not rebuild:
|
|
with open(cache) as f:
|
|
index = json.load(f)
|
|
console.print(f"[green]Episode-Index: {len(index)} Episoden geladen[/green]")
|
|
return index
|
|
|
|
console.print("\n[yellow]Baue Episode-Index auf (einmalig, danach gecacht)...[/yellow]")
|
|
index = ep_module.build_index(client, force=True)
|
|
console.print(f"[green]Episode-Index erstellt: {len(index)} Episoden[/green]")
|
|
return index
|
|
|
|
|
|
def _run(client, providers: list, session: dict, index: list):
|
|
episodes_context = ep_module.format_for_context(index)
|
|
|
|
# ── 1. Research ──────────────────────────────────────────────────────────
|
|
if session["step"] == "research":
|
|
author_input = research.get_author_input()
|
|
news_digest = research.research_news(client, author_input, episodes_context)
|
|
console.print(Panel(news_digest, title="Nachrichten-Kandidaten", border_style="green"))
|
|
|
|
session["research"] = {"author_input": author_input, "news_digest": news_digest}
|
|
session["step"] = "sources"
|
|
sess_module.save(session)
|
|
|
|
# ── 2. Source assessment ─────────────────────────────────────────────────
|
|
if session["step"] == "sources":
|
|
source_assessment = research.assess_sources(client, session["research"]["news_digest"])
|
|
console.print(Panel(source_assessment, title="Quellenbewertung", border_style="yellow"))
|
|
|
|
if not Confirm.ask("\nWeiter zur Storyline-Entwicklung?", default=True):
|
|
console.print("[dim]Session gespeichert. Neustart setzt hier fort.[/dim]")
|
|
return
|
|
|
|
session["sources"] = source_assessment
|
|
session["step"] = "storyline"
|
|
sess_module.save(session)
|
|
|
|
# ── 3. Storyline ─────────────────────────────────────────────────────────
|
|
if session["step"] == "storyline":
|
|
results = storyline.generate_storylines(
|
|
providers,
|
|
session["research"]["news_digest"],
|
|
session["sources"],
|
|
session["research"]["author_input"],
|
|
episodes_context,
|
|
)
|
|
provider_name, choice, selected_text, adjustments = storyline.select_storyline(results)
|
|
|
|
session["storyline"] = {
|
|
"results": results,
|
|
"selected_provider": provider_name,
|
|
"choice": choice,
|
|
"selected_text": selected_text,
|
|
"adjustments": adjustments,
|
|
}
|
|
session["step"] = "draft"
|
|
sess_module.save(session)
|
|
|
|
# ── 4. Draft ─────────────────────────────────────────────────────────────
|
|
if session["step"] == "draft":
|
|
sl = session["storyline"]
|
|
draft_results = drafting.generate_drafts(
|
|
providers,
|
|
sl["selected_text"],
|
|
sl["choice"],
|
|
sl["adjustments"],
|
|
session["research"]["news_digest"],
|
|
session["sources"],
|
|
session["research"]["author_input"],
|
|
)
|
|
provider_name, draft = drafting.select_draft(draft_results)
|
|
|
|
console.print("\n[dim]Überarbeitungshinweise? (Enter = Entwurf übernehmen)[/dim]")
|
|
feedback = input().strip()
|
|
if feedback:
|
|
draft = drafting.enrich_draft(client, draft, feedback)
|
|
console.print(Panel(draft, title="Überarbeiteter Entwurf", border_style="green"))
|
|
|
|
session["draft_results"] = draft_results
|
|
session["draft"] = draft
|
|
session["step"] = "quality"
|
|
sess_module.save(session)
|
|
|
|
# ── 5. Quality review ────────────────────────────────────────────────────
|
|
if session["step"] == "quality":
|
|
quality_report = quality.review(
|
|
client,
|
|
session["draft"],
|
|
session["sources"],
|
|
episodes_context,
|
|
)
|
|
console.print(Panel(quality_report, title="Qualitätsprüfung", border_style="yellow"))
|
|
|
|
console.print("\n[dim]Weitere Überarbeitungen? (Enter = weiter zur Publikation)[/dim]")
|
|
feedback = input().strip()
|
|
if feedback:
|
|
draft = drafting.enrich_draft(client, session["draft"], feedback)
|
|
session["draft"] = draft
|
|
console.print(Panel(draft, title="Finalisierter Entwurf", border_style="green"))
|
|
|
|
session["quality"] = quality_report
|
|
session["step"] = "publication"
|
|
sess_module.save(session)
|
|
|
|
# ── 6. Publication prep ──────────────────────────────────────────────────
|
|
if session["step"] == "publication":
|
|
teasers = publication.generate_teasers(client, session["draft"])
|
|
console.print(Panel(teasers, title="Teaser-Varianten", border_style="cyan"))
|
|
|
|
visual_ideas = publication.generate_visual_ideas(client, session["draft"])
|
|
console.print(Panel(visual_ideas, title="Cover-Ideen", border_style="cyan"))
|
|
|
|
out_path = publication.save_package(
|
|
session["date"],
|
|
session["draft"],
|
|
teasers,
|
|
session["sources"],
|
|
visual_ideas,
|
|
)
|
|
|
|
console.print(Panel(
|
|
f"[green]Publikationspaket gespeichert:[/green] {out_path}\n\n"
|
|
"Enthält: Artikel · Teaser-Varianten · Cover-Ideen · Quellenbewertung",
|
|
title="✓ Fertig",
|
|
border_style="green",
|
|
))
|
|
|
|
session["publication"] = {"teasers": teasers, "visual_ideas": visual_ideas}
|
|
session["step"] = "done"
|
|
sess_module.save(session)
|
|
|
|
|
|
def main():
|
|
_header()
|
|
|
|
try:
|
|
client = get_client()
|
|
except RuntimeError as e:
|
|
console.print(f"[red]{e}[/red]\nBitte ANTHROPIC_API_KEY in der .env-Datei eintragen.")
|
|
return
|
|
|
|
providers = get_configured()
|
|
|
|
if not providers:
|
|
console.print("[red]Keine LLM-API-Keys konfiguriert. Bitte .env-Datei prüfen.[/red]")
|
|
return
|
|
|
|
provider_names = ", ".join(p.name for p in providers)
|
|
console.print(f"[green]Aktive Modelle: {provider_names}[/green]")
|
|
|
|
index = _build_or_load_index(client)
|
|
|
|
latest = sess_module.load_latest()
|
|
session = None
|
|
if latest and latest.get("step") not in ("done",):
|
|
if Confirm.ask(
|
|
f"\nGefundene Session vom [bold]{latest['date']}[/bold] "
|
|
f"(Schritt: [bold]{latest['step']}[/bold]). Fortfahren?",
|
|
default=True,
|
|
):
|
|
session = latest
|
|
|
|
if session is None:
|
|
session = sess_module.new_session()
|
|
console.print(f"\n[green]Neue Session: {session['date']}[/green]")
|
|
|
|
_run(client, providers, session, index)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|