feat: assets et images, layouts image_split/image_full (C4)

This commit is contained in:
2026-07-09 21:50:30 +02:00
parent 70a1605ec0
commit 40b9451377
4 changed files with 787 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
patch_facilitator_c4.py — Chantier C4 (Assets & images)
=======================================================
Patch strict de facilitator_v9.py :
P1. validate_freeform accepte le nouveau type de bloc « image ».
P2. /lire inventorie aussi projets/<slug>/assets/ (images + dimensions)
et injecte la liste dans le contexte Narrator — le Narrator peut
alors référencer les fichiers par nom dans les layouts image_*.
Le dossier assets/ est créé au premier /lire s'il n'existe pas.
P3. Insère list_assets() (inventaire, dimensions via Pillow si
disponible, sinon noms seuls).
Usage (dossier du pipeline, single-line) :
python3 patch_facilitator_c4.py
Compatible tout ordre avec C1/C2/C3. Vérifie chaque ancre, écrit
.bak-fc4, compile, idempotent.
"""
import py_compile
import shutil
import sys
from pathlib import Path
TARGET = Path("facilitator_v9.py")
FUNC = '''
def list_assets(proj: "Project"):
"""Inventaire des images de projets/<slug>/assets/ (chantier C4).
Crée le dossier au premier appel. Retourne un bloc texte destiné au
contexte Narrator, ou une chaîne vide si aucune image."""
assets = proj.root / "assets"
assets.mkdir(exist_ok=True)
exts = {".png", ".jpg", ".jpeg", ".webp"}
files = sorted(p for p in assets.iterdir()
if p.suffix.lower() in exts and p.is_file())
if not files:
return ""
lines = ["IMAGES DISPONIBLES DANS assets/ (utilisables dans les "
"layouts image_split / image_full et le bloc freeform "
"image, par leur nom de fichier) :"]
for p in files:
dims = ""
try:
from PIL import Image
with Image.open(p) as im:
dims = f" ({im.size[0]}×{im.size[1]})"
except Exception:
pass
lines.append(f"- {p.name}{dims}")
return "\\n".join(lines)
'''
PATCHES = [
# P1 — type de bloc freeform « image »
(
'VALID_BLOCK_TYPES = {"title", "heading", "text", "stat", '
'"circle",\n'
' "badge", "card", "rect", "line"}\n',
'VALID_BLOCK_TYPES = {"title", "heading", "text", "stat", '
'"circle",\n'
' "badge", "card", "rect", "line", "image"}\n',
),
# P2 — /lire : inventaire des assets
(
' elif cmd == "/lire":\n'
' context, new_files = load_documents(proj, '
'loaded_docs)\n'
' if not new_files:\n'
' info("Aucun nouveau document dans inputs/.")\n'
' continue\n',
' elif cmd == "/lire":\n'
' context, new_files = load_documents(proj, '
'loaded_docs)\n'
' assets_info = list_assets(proj)\n'
' if assets_info:\n'
' context = ((context or "").strip()\n'
' + "\\n\\n" + assets_info).strip()\n'
' ok(f"{len(assets_info.splitlines()) - 1} '
'image(s) dans assets/.")\n'
' if not new_files and not assets_info:\n'
' info("Aucun nouveau document dans inputs/ ni '
'image dans assets/.")\n'
' continue\n',
),
]
MARKER = "def list_assets"
ANCHOR_SECTION = "# FLUX LIBRE — THE FREE DESIGNER"
def fail(msg):
print(" ! %s" % msg)
sys.exit(1)
def main():
if not TARGET.exists():
fail("%s introuvable — lancer depuis le dossier du pipeline."
% TARGET)
content = TARGET.read_text(encoding="utf-8")
if MARKER in content:
fail("Déjà patché (list_assets présent) — rien à faire.")
for i, (old, _) in enumerate(PATCHES, 1):
n = content.count(old)
if n == 0:
fail("Ancre du patch %d introuvable — facilitator inattendu."
% i)
if n > 1:
fail("Ancre du patch %d non unique (%d occurrences)." % (i, n))
if content.count(ANCHOR_SECTION) != 1:
fail("Ancre de section FREE DESIGNER introuvable ou non unique.")
shutil.copy2(TARGET, str(TARGET) + ".bak-fc4")
print(" + Sauvegarde : %s.bak-fc4" % TARGET)
for old, new in PATCHES:
content = content.replace(old, new)
lines = content.split("\n")
idx = next(i for i, l in enumerate(lines) if ANCHOR_SECTION in l)
ins = idx - 1 if lines[idx - 1].startswith("# ───") else idx
lines[ins:ins] = FUNC.split("\n")
content = "\n".join(lines)
TARGET.write_text(content, encoding="utf-8")
print(" + 2 patchs + list_assets appliqués.")
try:
py_compile.compile(str(TARGET), doraise=True)
print(" + Compilation OK.")
except py_compile.PyCompileError as e:
shutil.copy2(str(TARGET) + ".bak-fc4", TARGET)
fail("Erreur de compilation — fichier restauré :\n%s" % e)
print("\n Dépose tes images dans projets/<slug>/assets/ puis /lire "
"dans la session.")
if __name__ == "__main__":
main()