diff --git a/Dockerfile b/Dockerfile index 8ec94f0..68178b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,8 @@ RUN pip install --no-cache-dir torch --index-url https://download.pytorch.org/wh && pip install --no-cache-dir -r /app/backend/requirements.txt COPY --chown=app:app backend/ /app/backend/ +# katex fürs Parse-Gate (backend/katex_check.mjs prüft Formeln vor dem Speichern) +RUN cd /app/backend && npm install --no-audit --no-fund katex && chown -R app:app node_modules COPY --chown=app:app templates/ /app/templates/ COPY --chown=app:app --from=frontend /build/dist /app/frontend/dist COPY --chown=app:app dev-ops/opencode.json /home/app/.config/opencode/opencode.json diff --git a/Makefile b/Makefile index a341724..33330ae 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: install dev stop test test-e2e build +.PHONY: install dev stop test test-e2e build prod install: pip install --break-system-packages fastapi "uvicorn[standard]" httpx pytest pytest-asyncio ftfy fuzzysearch @@ -26,40 +26,7 @@ test-e2e: build: cd frontend && npm run build -# ── Server-Deployment (Muster vom alten creator; Traefik läuft dort schon) ──── -SERVER = root@178.104.67.87 -REMOTE = /var/www/creator2 - -# Code (+ .env) auf den Server spiegeln und Container neu bauen. -# storage/topics sind ausgenommen — Daten leben nur auf der jeweiligen Seite. -deploy: - rsync -avz --delete \ - --exclude .git --exclude node_modules --exclude frontend/node_modules \ - --exclude frontend/dist --exclude storage --exclude topics \ - --exclude .claude-data --exclude __pycache__ --exclude .pytest_cache \ - ./ $(SERVER):$(REMOTE)/ - ssh $(SERVER) 'cd $(REMOTE) && docker compose up -d --build' - @echo "Deploy fertig: https://creator2.marha.de" - -# Remote-Daten holen (Remote wird für den DB-Snapshot gestoppt und neu gestartet). -sync: stop - @rm -f storage/creator2.db-shm storage/creator2.db-wal - ssh $(SERVER) 'cd $(REMOTE) && docker compose down' - rsync -avz --progress $(SERVER):$(REMOTE)/storage/creator2.db storage/ - -rsync -avz --progress $(SERVER):$(REMOTE)/storage/creator2.db-wal storage/ - rsync -avz --progress --delete $(SERVER):$(REMOTE)/storage/korpus/ storage/korpus/ - rsync -avz --progress --delete $(SERVER):$(REMOTE)/topics/ topics/ - ssh $(SERVER) 'cd $(REMOTE) && docker compose up -d' - @echo "Sync abgeschlossen — Remote läuft wieder." - -# Lokalen Stand auf den Server schieben (überschreibt Remote-Daten!). -sync-reverse: stop - @[ -f storage/creator2.db ] || { echo "Keine lokale DB — abgebrochen."; exit 1; } - ssh $(SERVER) 'cd $(REMOTE) && docker compose down' - ssh $(SERVER) 'mkdir -p $(REMOTE)/storage $(REMOTE)/topics && rm -f $(REMOTE)/storage/creator2.db-shm $(REMOTE)/storage/creator2.db-wal' - rsync -avz --progress storage/creator2.db $(SERVER):$(REMOTE)/storage/ - -rsync -avz --progress storage/creator2.db-wal $(SERVER):$(REMOTE)/storage/ - rsync -avz --progress --delete storage/korpus/ $(SERVER):$(REMOTE)/storage/korpus/ - rsync -avz --progress --delete topics/ $(SERVER):$(REMOTE)/topics/ - ssh $(SERVER) 'cd $(REMOTE) && docker compose up -d' - @echo "Reverse-Sync abgeschlossen — Remote läuft wieder." +# ── Produktion (auf dem Server ausführen, im Projektverzeichnis) ────────────── +prod: + docker compose up -d --build + @echo "creator2 läuft: https://creator2.marha.de" diff --git a/backend/guide.py b/backend/guide.py index 8193d11..b618ec1 100644 --- a/backend/guide.py +++ b/backend/guide.py @@ -7,8 +7,12 @@ setzen (Lektion 57). Der Prüfer ist EIN verschmolzener Call (Fakten+Coverage, Lektion 52); deterministische Checks laufen davor im Code.""" import asyncio +import json import logging +import os import re +import subprocess +from pathlib import Path import db import llm @@ -17,6 +21,33 @@ from config import SECTION_WOERTER_PRO_ATOM log = logging.getLogger("creator2.guide") +_KATEX_SKRIPT = Path(__file__).resolve().parent / "katex_check.mjs" +_KATEX_BASE = Path(__file__).resolve().parent.parent / "frontend" +_katex_gewarnt = False + + +def _katex_fehler(formeln: list[dict]) -> list[str]: + """Parse-Gate: Formeln durch DENSELBEN Renderer wie das Frontend jagen. + Regex-Checks jagen eine offene Menge (heute \\textsc, morgen der nächste + unbekannte Befehl) — das Gate schließt die Klasse. Fake-Modus überspringt + (deterministisch + schnell); Unit-Test ruft direkt.""" + global _katex_gewarnt + if not formeln or os.getenv("CREATOR_FAKE_AGENTS"): + return [] + try: + res = subprocess.run( + ["node", str(_KATEX_SKRIPT)], input=json.dumps(formeln), + capture_output=True, text=True, timeout=30, + env={**os.environ, "KATEX_BASE": str(_KATEX_BASE)}) + if res.returncode: + raise RuntimeError(res.stderr.strip()[:200]) + return json.loads(res.stdout) + except Exception as e: # nie fail-open OHNE Signal (Befund-Prinzip) + if not _katex_gewarnt: + _katex_gewarnt = True + log.warning("KaTeX-Gate nicht verfügbar (%s) — Formel-Validierung fällt aus", e) + return [] + EBENE = "guide" _MARKER = re.compile(r"