From f17b1696d5b4e087029e77d329df12f1e8bdd230 Mon Sep 17 00:00:00 2001 From: Master Date: Tue, 14 Jul 2026 16:10:04 +0200 Subject: [PATCH] fix: cache-busting de la galerie d apercus (C9) --- templates/project.html | 2 +- webapp_sliding.py | 99 +++++++++++++++++++++++++++++------------- 2 files changed, 69 insertions(+), 32 deletions(-) diff --git a/templates/project.html b/templates/project.html index 3c787c1..6fc08d9 100644 --- a/templates/project.html +++ b/templates/project.html @@ -60,7 +60,7 @@ {% if previews %} diff --git a/webapp_sliding.py b/webapp_sliding.py index e5b62de..8af84df 100644 --- a/webapp_sliding.py +++ b/webapp_sliding.py @@ -191,15 +191,16 @@ async def project_page(request): entry = NARRATORS.get(slug) chat = entry["chat"] if entry else [] # previews existantes ? - previews = [] - pptx = info.get("dernier_pptx") + previews, cache_v = [], 0 + pptx = _pptx_path(slug) if pptx: - prev_dir = Path(pptx).parent / f"{Path(pptx).stem}_previews" + prev_dir = pptx.parent / f"{pptx.stem}_previews" + cache_v = int(pptx.stat().st_mtime) if prev_dir.is_dir(): previews = sorted(p.name for p in prev_dir.glob("slide-*.png")) return render("project.html", p=info, chat_html=_chat_html(chat), - previews=previews) + previews=previews, cache_v=cache_v) # ── routes : chat Narrator ─────────────────────────────────────────────────── @@ -285,28 +286,45 @@ async def formalise(request): entry = _narrator(slug) if not entry["started"]: return HTMLResponse('

Aucune conversation ' - 'Narrator — travaille le plan d\'abord.' - '

') + 'Narrator — travaille le plan d\'abord, ' + 'puis sauvegarde-le.

') + proj = fac.Project(slug) try: - md = entry["agent"].send( - "formalise — produis la formalisation complète slide par " - "slide (SLIDE N — titre, contenus, Notes éventuelles), " - "sans commentaire autour.") - entry["chat"].append(("system", "Formalisation demandée…")) + # Message IDENTIQUE à /formalise du facilitator (le Narrator + # produit un Markdown structuré #/##/### — PAS des "SLIDE N — + # layout" : ce sont le Designer puis l'Encoder qui les créent). + md = entry["agent"].feedback( + "Formalise maintenant le plan compact validé en Markdown " + "structuré complet pour le Designer (format #, ##, ###), " + "en développant le contenu de chaque slide. Inclus les " + "slides tiroir en les marquant [OPT-XXX].") + entry["chat"].append(("system", "Formalisation demandée.")) + entry["chat"].append(("narrator", md)) + # Garde : le Narrator a-t-il vraiment produit un plan ? + if md.count("#") < 3 or len(md) < 300: + return HTMLResponse( + '

Le Narrator n\'a pas renvoyé un plan ' + 'formalisé exploitable (réponse trop courte ou non ' + 'structurée). Vérifie le plan dans le chat, ajuste, ' + 'puis relance.

') + proj.mark_formalised(md) r = api.formalise(slug, md) except Exception as e: return HTMLResponse(f'

Échec : ' f'{html.escape(str(e))}

') if r.get("error"): - return HTMLResponse(f'

' - f'{html.escape(str(r))}

') + return HTMLResponse( + f'

{html.escape(str(r.get("error")))}' + f'
Le Designer/Encoder n\'a pas su ' + f'transcrire ce plan. Regarde la formalisation dans le ' + f'chat : elle doit lister les slides une à une.' + f'

') return HTMLResponse( f'

Deck généré : {r["slides"]} slides — ' f'{html.escape(r["validation"])}
' - f'Télécharger le PPTX · ' - f'

') + f'Télécharger le ' + f'PPTX puis « Générer les aperçus » ' + f'ci-dessus pour les voir.

') async def preview(request): @@ -325,6 +343,8 @@ async def preview(request): f'' for i, n in enumerate(r["slides"])) return HTMLResponse( + f'

{len(r["slides"])} aperçus générés. Coche des ' + f'slides pour les réviser.

' f'
' f'Slides {r["slides_revisees"]} révisées, deck ' - f'complet régénéré.
' - f'Télécharger ·

') + f'complet régénéré.
Télécharger puis ' + f'« Générer les aperçus » pour revoir le deck.

') # ── routes : fichiers ──────────────────────────────────────────────────────── +def _pptx_path(slug): + """Chemin ABSOLU du dernier PPTX. project_state.json stocke un + chemin relatif à la racine du pipeline — la webapp peut avoir un + cwd différent, on résout donc depuis BASE.""" + proj = fac.Project(slug) + raw = (proj.load_state().get("dernier_pptx") or "").strip() + if not raw: + return None + p = Path(raw) + if not p.is_absolute(): + p = BASE / p + return p if p.exists() else None + + async def preview_png(request): guard = require_login(request) if guard: return guard slug = request.path_params["slug"] name = request.path_params["name"] - proj = fac.Project(slug) - state = proj.load_state() - pptx = state.get("dernier_pptx") + pptx = _pptx_path(slug) if not pptx: return Response(status_code=404) - prev_dir = Path(pptx).parent / f"{Path(pptx).stem}_previews" + prev_dir = pptx.parent / f"{pptx.stem}_previews" try: png = _safe_child(prev_dir, name) except ValueError: return Response(status_code=403) if not png.exists(): return Response(status_code=404) - return FileResponse(str(png), media_type="image/png") + return FileResponse(str(png), media_type="image/png", + headers={"Cache-Control": "no-store"}) async def download(request): @@ -383,11 +415,16 @@ async def download(request): if guard: return guard slug = request.path_params["slug"] - proj = fac.Project(slug) - pptx = proj.load_state().get("dernier_pptx") - if not pptx or not Path(pptx).exists(): - return Response(status_code=404) - return FileResponse(pptx, filename=Path(pptx).name) + pptx = _pptx_path(slug) + if not pptx: + return HTMLResponse( + '

Aucun PPTX disponible pour ce projet — ' + 'lance « Formaliser le deck » d\'abord.

', + status_code=404) + return FileResponse( + str(pptx), filename=pptx.name, + media_type="application/vnd.openxmlformats-officedocument." + "presentationml.presentation") async def health(request):