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
+121
View File
@@ -0,0 +1,121 @@
"""Onglet "Générer avec l'IA" — plusieurs CONVERSATIONS par écran (CRUD,
comme claude.ai, voir plan) : routes/ia/ia_chat.py + screens/ia/.
ANTHROPIC_API_KEY est toujours absente en test (voir tests/conftest.py) :
ces tests exercent donc le chemin de DÉGRADATION GRACIEUSE (message
d'erreur clair, jamais une 500) pour l'envoi de message — pas un appel
réel, quel que soit le .env local du poste."""
import screens
def _create_jeu2d_game(client, tmp_game_slug_cleanup, name="pytest_ia_chat"):
resp = client.post("/games/new", data={"name": name}, follow_redirects=False)
slug = tmp_game_slug_cleanup(resp.headers["Location"].rstrip("/").split("/")[-1])
screen_id = screens.create_screen(slug, "Scène 1", kind="jeu_2d")
return slug, screen_id
def test_create_and_list_conversations(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
resp = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations")
assert resp.status_code == 200
conversation = resp.get_json()
assert conversation["title"] == "Nouvelle conversation"
resp2 = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations")
assert resp2.status_code == 200
ids = [c["id"] for c in resp2.get_json()]
assert conversation["id"] in ids
def test_conversations_are_isolated_per_screen(client, tmp_game_slug_cleanup):
slug, screen_id_1 = _create_jeu2d_game(client, tmp_game_slug_cleanup)
screen_id_2 = screens.create_screen(slug, "Scène 2", kind="jeu_2d")
client.post(f"/game/{slug}/screens/{screen_id_1}/ia-conversations")
resp = client.get(f"/game/{slug}/screens/{screen_id_2}/ia-conversations")
assert resp.get_json() == []
def test_delete_conversation_removes_it_from_the_list(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
resp = client.delete(f"/game/{slug}/ia-conversations/{conversation['id']}")
assert resp.status_code == 200
remaining = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
assert all(c["id"] != conversation["id"] for c in remaining)
def test_delete_unknown_conversation_404s(client, game):
resp = client.delete(f"/game/{game}/ia-conversations/999999")
assert resp.status_code == 404
def test_send_message_persists_it_and_a_graceful_error_reply(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
resp = client.post(
f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "Génère un dialogue"}
)
assert resp.status_code == 200
body = resp.get_json()
assert body["user_message"]["content"] == "Génère un dialogue"
assert body["user_message"]["role"] == "user"
assert body["assistant_message"]["role"] == "assistant"
assert "Configuration IA incomplète" in body["assistant_message"]["content"]
def test_send_message_rejects_an_empty_message(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
resp = client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": " "})
assert resp.status_code == 400
def test_send_message_404s_for_an_unknown_conversation(client, game):
resp = client.post(f"/game/{game}/ia-conversations/999999/messages", json={"message": "salut"})
assert resp.status_code == 404
def test_messages_history_is_persisted_and_ordered(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "premier"})
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "second"})
resp = client.get(f"/game/{slug}/ia-conversations/{conversation['id']}/messages")
assert resp.status_code == 200
messages = resp.get_json()
user_contents = [m["content"] for m in messages if m["role"] == "user"]
assert user_contents == ["premier", "second"]
def test_messages_are_isolated_per_conversation(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation_1 = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
conversation_2 = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
client.post(f"/game/{slug}/ia-conversations/{conversation_1['id']}/messages", json={"message": "pour la 1ère"})
resp = client.get(f"/game/{slug}/ia-conversations/{conversation_2['id']}/messages")
assert resp.get_json() == []
def test_first_user_message_renames_the_conversation(client, tmp_game_slug_cleanup):
slug, screen_id = _create_jeu2d_game(client, tmp_game_slug_cleanup)
conversation = client.post(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "Crée un fond de bureau"})
conversations = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
updated = next(c for c in conversations if c["id"] == conversation["id"])
assert updated["title"] == "Crée un fond de bureau"
# Un deuxième message ne renomme plus (titre déjà dérivé du premier).
client.post(f"/game/{slug}/ia-conversations/{conversation['id']}/messages", json={"message": "et une variable"})
conversations2 = client.get(f"/game/{slug}/screens/{screen_id}/ia-conversations").get_json()
updated2 = next(c for c in conversations2 if c["id"] == conversation["id"])
assert updated2["title"] == "Crée un fond de bureau"