Explorar el Código

Images : un seul repère en mémoire, erreur sur la première image

Les images d'un message tiennent en un seul repère dans la mémoire
(« 4 images jointes »). Si plusieurs images échouent, l'erreur nomme
la première dans l'ordre du message, pas la plus rapide à échouer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Johnounet hace 1 día
padre
commit
94b4278a31
Se han modificado 3 ficheros con 27 adiciones y 17 borrados
  1. 1 1
      CHANGELOG.md
  2. 19 16
      bot/discord_bot.py
  3. 7 0
      bot/history.py

+ 1 - 1
CHANGELOG.md

@@ -10,7 +10,7 @@ Les versions les plus récentes sont en haut. Chaque entrée commence par un ré
 
 ### Changements
 
-- **Plusieurs images dans un même message.** On peut envoyer jusqu'à 4 images à la fois : Rika les voit toutes, dans l'ordre, et peut les comparer ou parler de « la deuxième ». Les images d'un message cité ou transféré sont lues elles aussi, jusqu'à 4. Si une image pose problème, le message d'erreur dit laquelle. Un PDF reste seul, sans image.
+- **Plusieurs images dans un même message.** On peut envoyer jusqu'à 4 images à la fois : Rika les voit toutes, dans l'ordre, et peut les comparer ou parler de « la deuxième ». Les images d'un message cité ou transféré sont lues elles aussi, jusqu'à 4. Si une image pose problème, le message d'erreur dit laquelle (la première dans l'ordre du message si plusieurs échouent). Dans la mémoire, les images d'un message tiennent en un seul repère (« 4 images jointes »). Un PDF reste seul, sans image.
 
 - **Un message de démarrage plus soigné.** Le bloc « Bot en ligne » affiche maintenant l'avatar du bot, sa version, le modèle Claude utilisé (« Sonnet 5.5 ») et l'heure du démarrage, sur le vert du statut « en ligne » de Discord.
 

+ 19 - 16
bot/discord_bot.py

@@ -20,7 +20,7 @@ from . import VERSION
 from .claude_client import ClaudeClient, ClaudeError, ImageData
 from .config import Settings
 from .emojis import discord_emojis, emoji_names, short_emojis, usable_emojis
-from .history import IMAGE_MARKER, ConversationHistory, render_line
+from .history import ConversationHistory, images_marker, render_line
 from .images import ImageError, prepare_image
 from .members import MemberRegistry, with_username
 from .memory import DATE_FORMAT, MemoryStore
@@ -92,11 +92,11 @@ def text_file_marker(name: str) -> str:
     return f"[fichier texte joint « {name} » : tu l'as lu à ce moment-là, mais son contenu n'est plus conservé en mémoire]"
 
 
-def attachment_marker(attachment: ImageData) -> str:
-    """Remplace une image ou un PDF dans la mémoire (son contenu n'est pas conservé)."""
-    if attachment.is_pdf:
-        return f"[PDF joint « {attachment.name} » : tu l'as lu à ce moment-là, mais son contenu n'est plus conservé en mémoire]"
-    return IMAGE_MARKER
+def attachments_marker(attachments: list[ImageData]) -> str:
+    """Remplace les images ou le PDF d'un message dans la mémoire (leur contenu n'est pas conservé)."""
+    if attachments[0].is_pdf:
+        return f"[PDF joint « {attachments[0].name} » : tu l'as lu à ce moment-là, mais son contenu n'est plus conservé en mémoire]"
+    return images_marker(len(attachments))
 
 
 @dataclass
@@ -399,7 +399,7 @@ class ChatBot(commands.Bot):
                 line["reply_to"] = message.reference.message_id  # citation retirée si ce message est supprimé
                 line["reply"] = reply
             line["content"] = self.message_body(message)
-        markers = [attachment_marker(image) for image in images]
+        markers = [attachments_marker(images)] if images else []
         extra = ""
         if text_file:
             extra = text_file_prompt(*text_file)
@@ -754,15 +754,18 @@ class ChatBot(commands.Bot):
         return await asyncio.to_thread(prepare_image, data)
 
     async def download_images(self, attachments: list[discord.Attachment]) -> list[ImageData]:
-        """Télécharge et prépare plusieurs images en parallèle (lève ImageError si l'une pose problème)."""
-        async def download(attachment: discord.Attachment) -> ImageData:
-            try:
-                return await self.download_image(attachment)
-            except ImageError as exc:
-                if len(attachments) > 1:  # précise laquelle, sinon « cette image » est ambigu
-                    raise ImageError(f"« {attachment.filename} » : {exc.user_message}")
-                raise
-        return list(await asyncio.gather(*(download(a) for a in attachments)))
+        """Télécharge et prépare plusieurs images en parallèle (lève ImageError si l'une pose problème).
+
+        Si plusieurs échouent, l'erreur nomme la première dans l'ordre du message, pas la plus rapide.
+        """
+        results = await asyncio.gather(*(self.download_image(a) for a in attachments), return_exceptions=True)
+        for attachment, result in zip(attachments, results):
+            if isinstance(result, ImageError) and len(attachments) > 1:
+                # précise laquelle, sinon « cette image » est ambigu
+                raise ImageError(f"« {attachment.filename} » : {result.user_message}")
+            if isinstance(result, BaseException):
+                raise result
+        return list(results)
 
     # ------------------------------------------- message cité ou transféré
 

+ 7 - 0
bot/history.py

@@ -32,6 +32,13 @@ DELETED_REPLY = "en réponse à un message supprimé depuis"
 _DELETED_KEYS = ("reply_to", "reply", "forward", "content", "edited", "attachments")
 
 
+def images_marker(count: int) -> str:
+    """Remplace les images d'un message dans la mémoire : un seul repère, quel que soit leur nombre."""
+    if count == 1:
+        return IMAGE_MARKER
+    return f"[{count} images jointes : tu les as vues et analysées à ce moment-là, mais elles ne sont plus conservées en mémoire]"
+
+
 def render_line(line: dict) -> str:
     """Texte d'une ligne de la mémoire, tel qu'il est envoyé à Claude.