Ajoute l'assistant IA "Ruby" (Claude + Scenario) et corrige plusieurs bugs de la scène
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:
co-authored by
Claude Sonnet 5
parent
ab6635eee0
commit
b07b231a61
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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
|
||||
@@ -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))
|
||||
Reference in New Issue
Block a user