from typing import Any from flask import g, jsonify, request from werkzeug.wrappers import Response import ai import game_engine from core.flask_app import app _CONVERSATION_NOT_FOUND = {"error": "conversation introuvable"} def _get_owned_conversation(slug: str, conversation_id: int) -> dict[str, Any] | None: """La portée par SLUG (db.connect(slug), voir game_engine/ia/) suffit déjà à isoler les conversations d'un jeu à l'autre — un id d'une autre base n'existe simplement pas ici, même patron que game_engine.get_scene_object. Renvoie None si absente.""" return game_engine.get_ia_conversation(slug, conversation_id) @app.route("/game//screens//ia-conversations", methods=["GET"]) def ia_conversations_list(slug: str, screen_id: int) -> Response: """Colonne de gauche de l'onglet "Générer avec l'IA" — toutes les conversations de cet écran, plus récente en premier.""" return jsonify(game_engine.list_ia_conversations(slug, screen_id)) @app.route("/game//screens//ia-conversations", methods=["POST"]) def ia_conversations_create(slug: str, screen_id: int) -> Response: """ "+ Nouvelle conversation" — créée immédiatement, vide.""" conversation_id = game_engine.create_ia_conversation(slug, screen_id) conversation = game_engine.get_ia_conversation(slug, conversation_id) return jsonify(conversation) @app.route("/game//ia-conversations/", methods=["DELETE"]) def ia_conversation_delete(slug: str, conversation_id: int) -> Response | tuple[Response, int]: if not _get_owned_conversation(slug, conversation_id): return jsonify(_CONVERSATION_NOT_FOUND), 404 game_engine.delete_ia_conversation(slug, conversation_id) return jsonify({"ok": True}) @app.route("/game//ia-conversations//messages", methods=["GET"]) def ia_conversation_messages(slug: str, conversation_id: int) -> Response | tuple[Response, int]: """Historique complet d'UNE conversation — chargé par static/game/js/ia/ia-chat.js à sa sélection dans la colonne.""" if not _get_owned_conversation(slug, conversation_id): return jsonify(_CONVERSATION_NOT_FOUND), 404 return jsonify(game_engine.list_ia_chat_messages(slug, conversation_id)) @app.route("/game//ia-conversations//messages", methods=["POST"]) def ia_conversation_send(slug: str, conversation_id: int) -> Response | tuple[Response, int]: """Envoie un message dans une conversation — body {message}. Persiste le message du créateur et la réponse, renvoie les deux. ai.run_chat_turn lit l'historique déjà persisté et y ajoute le message courant EN MÉMOIRE seulement — persisté ici, APRÈS l'appel, jamais avant (sinon il apparaîtrait deux fois dans l'historique que la boucle envoie à Claude).""" conversation = _get_owned_conversation(slug, conversation_id) if not conversation: return jsonify(_CONVERSATION_NOT_FOUND), 404 message = (request.get_json(silent=True) or {}).get("message", "") message = message.strip() if isinstance(message, str) else "" if not message: return jsonify({"error": "message vide"}), 400 try: reply = ai.run_chat_turn(slug, conversation["screen_id"], conversation_id, g.current_user["id"], message) except (ai.AnthropicNotConfiguredError, ai.ScenarioNotConfiguredError, ai.ScenarioGenerationError) as e: reply = f"Configuration IA incomplète : {e}" except Exception: # Jamais le détail brut d'une erreur inattendue au créateur (même # politique que auth/send_email.py) — journalisée côté serveur # par le logger par défaut de Flask (DEBUG/erreur non interceptée). app.logger.exception("Erreur inattendue dans ai.run_chat_turn (%s, conversation %s)", slug, conversation_id) reply = "Une erreur inattendue est survenue — réessaie dans un instant." user_message_id = game_engine.add_ia_chat_message(slug, conversation_id, "user", message) assistant_message_id = game_engine.add_ia_chat_message(slug, conversation_id, "assistant", reply) return jsonify( { "user_message": {"id": user_message_id, "role": "user", "content": message}, "assistant_message": {"id": assistant_message_id, "role": "assistant", "content": reply}, } )