Ajoute l'assistant IA "Ruby" (Claude + Scenario) et corrige plusieurs bugs de la scène
Build and deploy / test-python (push) Successful in 12m24s
Build and deploy / test-js (push) Successful in 1m9s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Intègre un chat IA capable de manipuler la scène via les mêmes fonctions
que l'éditeur manuel (objets, variables, déclencheurs, images générées),
avec conversations multiples par écran façon Claude. Corrige au passage
le rafraîchissement pjax hors-ordre, l'onglet IA/déclencheurs vide après
sélection d'un objet, la comparaison de booléens dans les conditions, et
le blocage du glisser-déposer hors du cadre caméra après un redimensionnement
de fond par l'IA.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-09 19:40:31 +02:00
co-authored by Claude Sonnet 5
parent ab6635eee0
commit b07b231a61
62 changed files with 2928 additions and 62 deletions
+9
View File
@@ -30,6 +30,13 @@ from .update_profile import update_profile
from .count_admins import count_admins
from .delete_user import delete_user
from .update_email import update_email, EmailUpdateError
from .ensure_user_assets_schema import ensure_user_assets_schema
from .user_assets_dir import user_assets_dir
from .create_user_asset import create_user_asset
from .list_user_assets import list_user_assets
from .get_user_asset import get_user_asset
from .delete_user_asset import delete_user_asset
from .save_user_asset import save_user_asset
__all__ = [
"users_db_path", "ensure_users_schema", "is_first_user",
@@ -46,4 +53,6 @@ __all__ = [
"send_password_reset_email", "EmailNotConfiguredError",
"update_profile", "count_admins", "delete_user",
"update_email", "EmailUpdateError",
"ensure_user_assets_schema", "user_assets_dir", "create_user_asset",
"list_user_assets", "get_user_asset", "delete_user_asset", "save_user_asset",
]
+20
View File
@@ -0,0 +1,20 @@
from .connection import connect
from .ensure_user_assets_schema import ensure_user_assets_schema
def create_user_asset(user_id, filename, original_name=None, source="upload"):
"""Enregistre une ligne "Mes assets" pour un fichier déjà écrit sur
disque (voir user_assets_dir.py) — le fichier lui-même est écrit par
l'appelant (route d'upload, ou plus tard la génération IA), cette
fonction ne fait que la partie base de données, comme
db.create_global_variable pour le reste du moteur."""
ensure_user_assets_schema()
conn = connect()
conn.execute(
"INSERT INTO _user_assets (user_id, filename, original_name, source) VALUES (?, ?, ?, ?)",
(user_id, filename, original_name, source),
)
asset_id = conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
conn.commit()
conn.close()
return asset_id
+27
View File
@@ -0,0 +1,27 @@
import os
from .connection import connect
from .ensure_user_assets_schema import ensure_user_assets_schema
from .user_assets_dir import user_assets_dir
def delete_user_asset(asset_id, user_id):
"""N'efface que si asset_id APPARTIENT à user_id (jamais fournie par
l'appelant sans vérification — voir get_user_asset.py) : renvoie False
sans rien faire sinon, plutôt que de lever une erreur, même patron que
screens.delete_scene_object pour un id introuvable."""
ensure_user_assets_schema()
conn = connect()
row = conn.execute(
"SELECT filename FROM _user_assets WHERE id = ? AND user_id = ?", (asset_id, user_id)
).fetchone()
if not row:
conn.close()
return False
conn.execute("DELETE FROM _user_assets WHERE id = ?", (asset_id,))
conn.commit()
conn.close()
path = os.path.join(user_assets_dir(user_id), row["filename"])
if os.path.exists(path):
os.remove(path)
return True
+23
View File
@@ -0,0 +1,23 @@
from .connection import connect
def ensure_user_assets_schema():
""""Mes assets" (voir user_assets_dir.py) : une image appartient à un
COMPTE, jamais à un projet — table dans la base de comptes partagée
(auth/connection.py), pas dans le game.db d'un jeu (db/connection.py),
pour rester utilisable d'un projet à l'autre du même compte."""
conn = connect()
conn.executescript(
"""
CREATE TABLE IF NOT EXISTS _user_assets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
filename TEXT NOT NULL,
original_name TEXT,
source TEXT NOT NULL DEFAULT 'upload' CHECK (source IN ('upload', 'ia')),
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
"""
)
conn.commit()
conn.close()
+17
View File
@@ -0,0 +1,17 @@
from .connection import connect
from .ensure_user_assets_schema import ensure_user_assets_schema
def get_user_asset(asset_id):
"""Renvoie aussi user_id — indispensable pour vérifier la PROPRIÉTÉ
avant de servir/poser un asset (voir routes/assets/serve_user_asset.py,
routes/scenes/scene_object_add.py), même esprit que l'isolation par
propriétaire déjà en place sur les projets (core/auth_guard.py)."""
ensure_user_assets_schema()
conn = connect()
row = conn.execute(
"SELECT id, user_id, filename, original_name, source, created_at FROM _user_assets WHERE id = ?",
(asset_id,),
).fetchone()
conn.close()
return dict(row) if row else None
+17
View File
@@ -0,0 +1,17 @@
from .connection import connect
from .ensure_user_assets_schema import ensure_user_assets_schema
def list_user_assets(user_id):
""""Mes assets" d'un compte, plus récent en premier — voir
templates/scene_edit.html, bloc "Mes assets" du panneau d'ajout
d'élément."""
ensure_user_assets_schema()
conn = connect()
rows = conn.execute(
"SELECT id, user_id, filename, original_name, source, created_at "
"FROM _user_assets WHERE user_id = ? ORDER BY id DESC",
(user_id,),
).fetchall()
conn.close()
return [dict(row) for row in rows]
+20
View File
@@ -0,0 +1,20 @@
import os
import uuid
from .create_user_asset import create_user_asset
from .user_assets_dir import user_assets_dir
def save_user_asset(user_id, content, ext, original_name=None, source="upload"):
"""Écrit `content` (bytes) sur disque + crée la ligne "Mes assets" —
factorisé pour être partagé par l'upload manuel
(routes/assets/upload_user_asset.py) ET la génération IA
(ai/tools.py::_dispatch_add_generated_image), jamais dupliqué entre
les deux. Renvoie (asset_id, filename)."""
filename = uuid.uuid4().hex + ext
asset_dir = user_assets_dir(user_id)
os.makedirs(asset_dir, exist_ok=True)
with open(os.path.join(asset_dir, filename), "wb") as f:
f.write(content)
asset_id = create_user_asset(user_id, filename, original_name=original_name, source=source)
return asset_id, filename
+10
View File
@@ -0,0 +1,10 @@
import os
from db.constants import USER_ASSETS_DIR
def user_assets_dir(user_id):
"""Dossier disque des images d'un compte (voir ensure_user_assets_schema.py
pour les métadonnées) — mirroir de db/game_dir.py, mais indexé par
utilisateur plutôt que par slug de projet."""
return os.path.join(USER_ASSETS_DIR, str(user_id))