From 81913f3d8db6aa84dc2595cd64dd82cbd30a9e1e Mon Sep 17 00:00:00 2001 From: root Date: Thu, 28 May 2026 15:20:25 +0000 Subject: [PATCH] update --- Makefile | 9 ++- backend/config.py | 8 --- backend/generator.py | 132 +++++++------------------------------------ backend/main.py | 1 - 4 files changed, 27 insertions(+), 123 deletions(-) diff --git a/Makefile b/Makefile index dbcfb86..d60d453 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install dev prod stop logs remove auth +.PHONY: install dev prod stop logs remove auth sync COMPOSE = docker compose @@ -35,3 +35,10 @@ remove: stop @echo "Lösche Datenbank und generierte Dateien..." rm -rf storage/* @echo "Fertig." + +sync: + @mkdir -p storage/html storage/pdf + rsync -avz --progress root@178.104.67.87:/var/www/guides/storage/guides.db storage/ + rsync -avz --progress --delete root@178.104.67.87:/var/www/guides/storage/html/ storage/html/ + rsync -avz --progress --delete root@178.104.67.87:/var/www/guides/storage/pdf/ storage/pdf/ + @echo "Sync abgeschlossen." diff --git a/backend/config.py b/backend/config.py index 1dfedf4..f884bb9 100644 --- a/backend/config.py +++ b/backend/config.py @@ -27,12 +27,4 @@ FORMAT_META = { AGENT_TIMEOUT = 3600 MAX_CONCURRENT_GENERATIONS = 6 -CONTENT_REVIEW_ITERATIONS = { - "OnePager": 1, - "Cheatsheet": 1, - "MiniGuide": 2, - "BeginnerGuide": 2, - "IntermediateGuide": 2, - "ExtendedGuide": 2, -} CLAUDE_CLI = "claude" diff --git a/backend/generator.py b/backend/generator.py index 82f9b85..78724c5 100644 --- a/backend/generator.py +++ b/backend/generator.py @@ -10,7 +10,6 @@ from pathlib import Path from config import ( AGENT_TIMEOUT, CLAUDE_CLI, - CONTENT_REVIEW_ITERATIONS, TEMPLATES_DIR, MAX_CONCURRENT_GENERATIONS, STORAGE_DIR, @@ -86,19 +85,6 @@ async def _render_pdf(html_path: Path, pdf_path: Path) -> tuple[bool, str]: return True, "" -async def _render_pngs(pdf_path: Path, preview_dir: Path) -> list[Path]: - preview_dir.mkdir(parents=True, exist_ok=True) - proc = await asyncio.create_subprocess_exec( - "python3", "-c", - f"from pdf2image import convert_from_path; pages = convert_from_path('{pdf_path}', dpi=120); [p.save('{preview_dir}/page_' + str(i) + '.png') for i, p in enumerate(pages)]; print(len(pages))", - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - ) - stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=60) - pngs = sorted(preview_dir.glob("page_*.png")) - return pngs - - def _build_generator_prompt(topic: str, format_name: str, html_path: Path, instructions: str = "") -> str: spec = (TEMPLATES_DIR / "Format" / f"{format_name}.md").read_text(encoding="utf-8") reference = (TEMPLATES_DIR / "Referenz" / f"{format_name}.md").read_text(encoding="utf-8") @@ -183,43 +169,6 @@ FAIL """ -def _build_review_prompt(format_name: str, png_paths: list[Path], page_count: int) -> str: - spec = (TEMPLATES_DIR / "Format" / f"{format_name}.md").read_text(encoding="utf-8") - - read_instructions = "\n".join( - f"- Öffne mit dem Read-Tool: {p}" for p in png_paths - ) - - return f"""Prüfe visuell einen generierten "{format_name}" Guide. - -SCHRITT 1 — Bilder laden: -Das PDF hat {page_count} Seite(n), gerendert als PNG-Screenshots. -Nutze das Read-Tool, um JEDE der folgenden Dateien zu öffnen und visuell zu inspizieren: -{read_instructions} - -SCHRITT 2 — Visuell prüfen anhand dieser Spezifikation: -{spec} - -Prüfkriterien (basierend auf dem, was du in den Bildern SIEHST): -- Stimmt die Seitenanzahl? (OnePager/Cheatsheet = exakt 1 Seite) -- Ist Text abgeschnitten, überlappt oder läuft aus dem sichtbaren Bereich? -- Fehlen Pflicht-Elemente (Cover, TOC, Recall-Boxen, Callouts, etc.)? -- Sind Code-Blöcke über Seitenumbrüche zerrissen? -- Ist das Layout korrekt (Spalten, Grid, Footer)? - -SCHRITT 3 — Antworte mit GENAU EINEM der folgenden Formate: - -Bei Bestehen: -PASS - -Bei Nicht-Bestehen: -FAIL -- Problem 1 -- Problem 2 -- ... -""" - - async def generate_guide(guide_id: str, topic: str, format_name: str, instructions: str = "") -> None: async with _semaphore: now = datetime.now(timezone.utc).isoformat() @@ -227,8 +176,6 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio html_path = STORAGE_DIR / "html" / f"{guide_id}.html" pdf_path = STORAGE_DIR / "pdf" / f"{guide_id}.pdf" - preview_dir = STORAGE_DIR / "preview" / guide_id - content_iters = CONTENT_REVIEW_ITERATIONS.get(format_name, 2) try: if guide_id in _cancelled: @@ -252,31 +199,28 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio await _fail(guide_id, "HTML-Datei wurde nicht erstellt") return - # Phase 1: Inhalts-Review (HTML lesen + Websuche, kein PDF) - for iteration in range(1, content_iters + 1): + # Step 2: Inhalts-Review (1x, kein Loop) + if guide_id in _cancelled: + return + + await _set_progress(guide_id, "Prüfe Inhalt…") + current_step = "Inhalts-Review" + current_timeout = AGENT_TIMEOUT + content_prompt = _build_content_review_prompt(topic, format_name, html_path) + returncode, review_out, review_err = await _run_claude(guide_id, content_prompt, AGENT_TIMEOUT) + + if returncode != 0: + await _fail(guide_id, f"Inhalts-Review-Fehler: {review_err[:1000]}") + return + + review_text = review_out.strip() + if not review_text.startswith("PASS"): if guide_id in _cancelled: return - await _set_progress(guide_id, f"Prüfe Inhalt… ({iteration}/{content_iters})") - current_step = f"Inhalts-Review ({iteration}/{content_iters})" - current_timeout = AGENT_TIMEOUT - content_prompt = _build_content_review_prompt(topic, format_name, html_path) - returncode, review_out, review_err = await _run_claude(guide_id, content_prompt, AGENT_TIMEOUT) - - if returncode != 0: - await _fail(guide_id, f"Inhalts-Review-Fehler: {review_err[:1000]}") - return - - review_text = review_out.strip() - if review_text.startswith("PASS"): - break - - if iteration == content_iters: - break - feedback = review_text.replace("FAIL", "").strip() - await _set_progress(guide_id, f"Korrigiere Inhalt… ({iteration}/{content_iters})") - current_step = f"Inhalts-Korrektur ({iteration}/{content_iters})" + await _set_progress(guide_id, "Korrigiere Inhalt…") + current_step = "Inhalts-Korrektur" current_timeout = AGENT_TIMEOUT fix_prompt = _build_fix_prompt(topic, format_name, html_path, feedback) returncode, _, fix_err = await _run_claude(guide_id, fix_prompt, AGENT_TIMEOUT) @@ -285,7 +229,7 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio await _fail(guide_id, f"Fix-Fehler: {fix_err[:1000]}") return - # Phase 2: Stil-Review (1x PDF rendern + PNGs prüfen) + # Step 3: PDF rendern if guide_id in _cancelled: return @@ -295,39 +239,6 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio await _fail(guide_id, f"WeasyPrint-Fehler: {err}") return - await _set_progress(guide_id, "Prüfe Layout…") - pngs = await _render_pngs(pdf_path, preview_dir) - page_count = len(pngs) - - current_step = "Stil-Review" - current_timeout = AGENT_TIMEOUT - review_prompt = _build_review_prompt(format_name, pngs, page_count) - returncode, review_out, review_err = await _run_claude(guide_id, review_prompt, AGENT_TIMEOUT) - - if returncode != 0: - await _fail(guide_id, f"Stil-Review-Fehler: {review_err[:1000]}") - return - - review_text = review_out.strip() - if not review_text.startswith("PASS"): - feedback = review_text.replace("FAIL", "").strip() - await _set_progress(guide_id, "Korrigiere Layout…") - current_step = "Stil-Korrektur" - current_timeout = AGENT_TIMEOUT - fix_prompt = _build_fix_prompt(topic, format_name, html_path, feedback) - returncode, _, fix_err = await _run_claude(guide_id, fix_prompt, AGENT_TIMEOUT) - - if returncode != 0: - await _fail(guide_id, f"Fix-Fehler: {fix_err[:1000]}") - return - - # Finales PDF nach Stil-Fix - await _set_progress(guide_id, "Rendere finales PDF…") - ok, err = await _render_pdf(html_path, pdf_path) - if not ok: - await _fail(guide_id, f"WeasyPrint-Fehler: {err}") - return - now = datetime.now(timezone.utc).isoformat() await update_guide( guide_id, @@ -345,11 +256,6 @@ async def generate_guide(guide_id: str, topic: str, format_name: str, instructio finally: _active_processes.pop(guide_id, None) _cancelled.discard(guide_id) - # Preview-PNGs aufräumen - if preview_dir.exists(): - for f in preview_dir.glob("*"): - f.unlink() - preview_dir.rmdir() async def rework_guide(guide_id: str, topic: str, format_name: str, instructions: str) -> None: diff --git a/backend/main.py b/backend/main.py index 3073150..f63f085 100644 --- a/backend/main.py +++ b/backend/main.py @@ -12,7 +12,6 @@ from routes import router async def lifespan(app: FastAPI): (STORAGE_DIR / "html").mkdir(parents=True, exist_ok=True) (STORAGE_DIR / "pdf").mkdir(parents=True, exist_ok=True) - (STORAGE_DIR / "preview").mkdir(parents=True, exist_ok=True) await init_db() yield await close_db()