diff --git a/API.md b/API.md index 47ae955..c04eee6 100644 --- a/API.md +++ b/API.md @@ -17,9 +17,10 @@ Ohne `v2` gilt die neueste Version. - `POST /api/themen` `{name, hinweise, spezialisierung}` → Thema wie oben (legt `v1` an) – `name`: Hauptthema; `spezialisierung`: grenzt es ein, nur was dafür gebraucht wird; die Bezeichnung (`name` in der Antwort, Ordner) wird „Thema – Spezialisierung“ – `hinweise`: wie das Thema zu verstehen ist; gelten je Version und sind bei einer neuen Version änderbar + – `stopp_gliederung` (bool, optional): der Lauf hält nach der Gliederung an (`pausiert`); „Fortsetzen“ schreibt die Kapitel - `GET /api/themen/{slug}` → Thema wie oben - `DELETE /api/themen/{slug}` – ganzes Thema -- `POST /api/themen/{slug}/versionen` `{hinweise?}` → `{"v": N}` – nächste Version ohne Start; ohne Angabe übernimmt sie die Hinweise der neuesten Version +- `POST /api/themen/{slug}/versionen` `{hinweise?, stopp_gliederung?}` → `{"v": N}` – nächste Version ohne Start; ohne Angabe übernimmt sie die Hinweise der neuesten Version - `DELETE /api/themen/{slug}/v{N}` – nur diese Version - `POST /api/themen/{slug}/v{N}/start` – startet oder setzt fort (resümiert an der offenen Stufe) - `POST /api/themen/{slug}/v{N}/pause` – bricht laufende Agenten ab; Neustart mit `start` @@ -95,7 +96,7 @@ Solange ein Lauf läuft, enthält `buch` die bereits geschriebenen Kapitel (Stat storage// thema.json {name, thema, spezialisierung, slug, titel, hinweise, angelegt} – name = „Thema – Spezialisierung“ v1/, v2/, … je Version: - state.json Zustand wie oben (ohne Buch), mit den Hinweisen der Version + state.json Zustand wie oben (ohne Buch), mit hinweise und stopp_gliederung der Version deutung.json vom Recherche-Agenten position.json Leseposition quellen/ QUELLEN.md + geklonte/geladene Quellen diff --git a/backend/main.py b/backend/main.py index 72d7fdd..41496c3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -30,10 +30,12 @@ class ThemaNeu(BaseModel): name: str hinweise: str = "" spezialisierung: str = "" + stopp_gliederung: bool = False class VersionNeu(BaseModel): hinweise: str | None = None # None: wie die bisherige Version + stopp_gliederung: bool = False class Position(BaseModel): @@ -89,7 +91,7 @@ async def thema_anlegen(body: ThemaNeu): if not state.name_ok(name) or not state.name_ok(state.bezeichnung(name, body.spezialisierung.strip())): raise HTTPException(400, "Ungültiger Name") try: - return _thema_info(state.anlegen(name, body.hinweise, body.spezialisierung)) + return _thema_info(state.anlegen(name, body.hinweise, body.spezialisierung, body.stopp_gliederung)) except ValueError as e: raise HTTPException(409, str(e)) @@ -112,7 +114,7 @@ async def thema_loeschen(slug: str): async def version_anlegen(slug: str, body: VersionNeu | None = None): """Neue Version: frischer Lauf ohne Wissen über die Vorgänger; Hinweise wahlweise geändert.""" b = body or VersionNeu() - key = state.version_anlegen(_thema(slug)["name"], b.hinweise) + key = state.version_anlegen(_thema(slug)["name"], b.hinweise, b.stopp_gliederung) return {"v": int(key.rsplit("/v", 1)[1])} diff --git a/backend/pipeline.py b/backend/pipeline.py index 4aa9f75..d6c0eae 100644 --- a/backend/pipeline.py +++ b/backend/pipeline.py @@ -50,6 +50,9 @@ async def _lauf(name: str) -> None: await STUFEN_FN[sid](name) s.update(status="fertig", beendet=state.jetzt()) state.speichern(name) + if sid == "gliederung" and st.get("stopp_gliederung"): + st.update(status="pausiert", grund="Nach der Gliederung angehalten – „Fortsetzen“ schreibt die Kapitel", stufe="") + return st.update(status="fertig", grund="", stufe="") except asyncio.CancelledError: st.update(status="pausiert", grund="Pausiert") diff --git a/backend/state.py b/backend/state.py index 4e3224f..691b7ef 100644 --- a/backend/state.py +++ b/backend/state.py @@ -55,15 +55,17 @@ def name_von_slug(sl: str) -> str | None: return next((t["name"] for t in themen() if t.get("slug") == sl), None) -def version_anlegen(name: str, hinweise: str | None = None) -> str: - """Neue Version; ohne Angabe übernimmt sie die Hinweise der neuesten Version (bzw. des Themas).""" +def version_anlegen(name: str, hinweise: str | None = None, stopp_gliederung: bool = False) -> str: + """Neue Version; ohne Angabe übernimmt sie die Hinweise der neuesten Version (bzw. des Themas). + stopp_gliederung: der Lauf hält nach der Gliederung an (günstiger Testlauf), „Fortsetzen“ schreibt weiter.""" vs = versionen(name) alt = hinweise_von(f"{name}/v{vs[-1]}") if vs else (thema(name) or {}).get("hinweise", "") key = f"{name}/v{(vs or [0])[-1] + 1}" v = verzeichnis(key) for u in ("quellen", "buch", "berichte", "arbeit"): (v / u).mkdir(parents=True) - _schreiben(v / "state.json", {**leerer_zustand(), "hinweise": (alt if hinweise is None else hinweise).strip()}) + _schreiben(v / "state.json", {**leerer_zustand(), "hinweise": (alt if hinweise is None else hinweise).strip(), + "stopp_gliederung": stopp_gliederung}) return key @@ -137,7 +139,7 @@ def themen() -> list[dict]: return aus -def anlegen(name: str, hinweise: str, spezialisierung: str = "") -> dict: +def anlegen(name: str, hinweise: str, spezialisierung: str = "", stopp_gliederung: bool = False) -> dict: """name ist das Hauptthema („Game Design“), die Spezialisierung grenzt es ein; beides zusammen bildet die Bezeichnung.""" spezialisierung = spezialisierung.strip() bez = bezeichnung(name, spezialisierung) @@ -148,7 +150,7 @@ def anlegen(name: str, hinweise: str, spezialisierung: str = "") -> dict: t = {"name": bez, "thema": name, "spezialisierung": spezialisierung, "slug": slug_frei(bez), "titel": bez, "hinweise": hinweise.strip(), "angelegt": jetzt()} _schreiben(d / "thema.json", t) - version_anlegen(bez) + version_anlegen(bez, stopp_gliederung=stopp_gliederung) return t @@ -160,14 +162,14 @@ def loeschen(name: str) -> None: def zuruecksetzen(key: str) -> None: v = verzeichnis(key) - h = hinweise_von(key) + h, stopp = hinweise_von(key), bool(zustand(key).get("stopp_gliederung")) _cache.pop(key, None) for u in ("quellen", "buch", "berichte", "arbeit"): shutil.rmtree(v / u, ignore_errors=True) (v / u).mkdir() for f in ("deutung.json", "log.jsonl", "state.json", "position.json"): (v / f).unlink(missing_ok=True) - _schreiben(v / "state.json", {**leerer_zustand(), "hinweise": h}) + _schreiben(v / "state.json", {**leerer_zustand(), "hinweise": h, "stopp_gliederung": stopp}) def thema(key: str) -> dict | None: diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0266fcf..8018cab 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -36,6 +36,7 @@ const fehler = ref('') const neuName = ref('') const neuHinweise = ref('') const neuSpezialisierung = ref('') +const neuStopp = ref(false) const navAuf = ref(false) let timer = null @@ -105,10 +106,11 @@ async function anlegen() { const name = neuName.value.trim() if (!name) return try { - const t = await api.anlegen(name, neuHinweise.value.trim(), neuSpezialisierung.value.trim()) + const t = await api.anlegen(name, neuHinweise.value.trim(), neuSpezialisierung.value.trim(), neuStopp.value) neuName.value = '' neuHinweise.value = '' neuSpezialisierung.value = '' + neuStopp.value = false await topicsLaden() themaWaehlen(t.slug) } catch (e) { fehler.value = String(e.message || e) } @@ -173,6 +175,7 @@ function tokens(n) { return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? M + diff --git a/frontend/src/api.js b/frontend/src/api.js index a14fbaa..fb35825 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -18,7 +18,7 @@ const e = encodeURIComponent const v = (slug, n) => `/themen/${e(slug)}/v${n}` export const api = { themen: () => req('/themen'), - anlegen: (name, hinweise = '', spezialisierung = '') => req('/themen', { method: 'POST', body: { name, hinweise, spezialisierung } }), + anlegen: (name, hinweise = '', spezialisierung = '', stopp_gliederung = false) => req('/themen', { method: 'POST', body: { name, hinweise, spezialisierung, stopp_gliederung } }), loeschen: (slug) => req(`/themen/${e(slug)}`, { method: 'DELETE' }), versionAnlegen: (slug, eingaben) => req(`/themen/${e(slug)}/versionen`, { method: 'POST', body: eingaben }), versionLoeschen: (slug, n) => req(v(slug, n), { method: 'DELETE' }), diff --git a/frontend/src/components/Lauf.vue b/frontend/src/components/Lauf.vue index bd8e2f4..8d2716d 100644 --- a/frontend/src/components/Lauf.vue +++ b/frontend/src/components/Lauf.vue @@ -39,8 +39,9 @@ function reset() { if (confirm('Lauf zurücksetzen? Alles außer dem Thema wird // Neue Version: Formular mit den Hinweisen dieser Version vorbelegt; Thema und Spezialisierung bleiben. const neuOffen = ref(false) const neuHinweise = ref('') -function neuOeffnen() { neuHinweise.value = props.state.hinweise || ''; neuOffen.value = true } -function neuStarten() { neuOffen.value = false; emit('neue-version', { hinweise: neuHinweise.value.trim() }) } +const neuStopp = ref(false) +function neuOeffnen() { neuHinweise.value = props.state.hinweise || ''; neuStopp.value = !!props.state.stopp_gliederung; neuOffen.value = true } +function neuStarten() { neuOffen.value = false; emit('neue-version', { hinweise: neuHinweise.value.trim(), stopp_gliederung: neuStopp.value }) }