chatbot.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. import os
  2. import base64
  3. import logging
  4. import re
  5. from io import BytesIO
  6. import discord
  7. from dotenv import load_dotenv
  8. from PIL import Image
  9. import emoji
  10. import tiktoken
  11. from openai import AsyncOpenAI, OpenAIError
  12. import json
  13. # Charger les variables d'environnement depuis le fichier .env
  14. load_dotenv()
  15. DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
  16. OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
  17. DISCORD_CHANNEL_ID = os.getenv('DISCORD_CHANNEL_ID')
  18. PERSONALITY_PROMPT_FILE = os.getenv('PERSONALITY_PROMPT_FILE', 'personality_prompt.txt')
  19. CONVERSATION_HISTORY_FILE = os.getenv('CONVERSATION_HISTORY_FILE', 'conversation_history.json')
  20. BOT_NAME = os.getenv('BOT_NAME', 'ChatBot')
  21. # Initialiser le client OpenAI asynchrone ici
  22. openai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
  23. BOT_VERSION = "2.4.2"
  24. # Vérifier que les tokens et le prompt de personnalité sont récupérés
  25. if DISCORD_TOKEN is None or OPENAI_API_KEY is None or DISCORD_CHANNEL_ID is None:
  26. raise ValueError("Les tokens ou l'ID du canal ne sont pas définis dans les variables d'environnement.")
  27. if not os.path.isfile(PERSONALITY_PROMPT_FILE):
  28. raise FileNotFoundError(f"Le fichier de prompt de personnalité '{PERSONALITY_PROMPT_FILE}' est introuvable.")
  29. # Lire le prompt de personnalité depuis le fichier
  30. with open(PERSONALITY_PROMPT_FILE, 'r', encoding='utf-8') as f:
  31. PERSONALITY_PROMPT = f.read().strip()
  32. # Log configuration
  33. log_format = '%(asctime)-13s : %(name)-15s : %(levelname)-8s : %(message)s'
  34. logging.basicConfig(handlers=[logging.FileHandler("./chatbot.log", 'a', 'utf-8')], format=log_format, level="INFO")
  35. console = logging.StreamHandler()
  36. console.setLevel(logging.INFO)
  37. console.setFormatter(logging.Formatter(log_format))
  38. logger = logging.getLogger(BOT_NAME)
  39. logger.setLevel("INFO")
  40. logging.getLogger('').addHandler(console)
  41. httpx_logger = logging.getLogger('httpx')
  42. httpx_logger.setLevel(logging.WARNING)
  43. # Initialiser les intents
  44. intents = discord.Intents.default()
  45. intents.message_content = True # Activer l'intent pour les contenus de message
  46. # Liste pour stocker l'historique des conversations
  47. conversation_history = []
  48. def load_conversation_history():
  49. global conversation_history
  50. if os.path.isfile(CONVERSATION_HISTORY_FILE):
  51. try:
  52. with open(CONVERSATION_HISTORY_FILE, 'r', encoding='utf-8') as f:
  53. loaded_history = json.load(f)
  54. # Exclure uniquement le PERSONALITY_PROMPT
  55. conversation_history = [
  56. msg for msg in loaded_history
  57. if not (msg.get("role") == "system" and msg.get("content") == PERSONALITY_PROMPT)
  58. ]
  59. logger.info(f"Historique chargé depuis {CONVERSATION_HISTORY_FILE}")
  60. except Exception as e:
  61. logger.error(f"Erreur lors du chargement de l'historique : {e}")
  62. conversation_history = []
  63. else:
  64. logger.info(f"Aucun fichier d'historique trouvé. Un nouveau fichier sera créé à {CONVERSATION_HISTORY_FILE}")
  65. def has_text(text):
  66. """
  67. Détermine si le texte fourni est non vide après suppression des espaces.
  68. """
  69. return bool(text.strip())
  70. # Fonction de sauvegarde de l'historique
  71. def save_conversation_history():
  72. try:
  73. with open(CONVERSATION_HISTORY_FILE, 'w', encoding='utf-8') as f:
  74. json.dump(conversation_history, f, ensure_ascii=False, indent=4)
  75. except Exception as e:
  76. logger.error(f"Erreur lors de la sauvegarde de l'historique : {e}")
  77. # Charger l'encodeur pour le modèle GPT-4o mini
  78. encoding = tiktoken.get_encoding("o200k_base")
  79. # Convertir l'ID du channel en entier
  80. try:
  81. chatgpt_channel_id = int(DISCORD_CHANNEL_ID)
  82. except ValueError:
  83. raise ValueError("L'ID du channel Discord est invalide. Assurez-vous qu'il s'agit d'un entier.")
  84. class MyDiscordClient(discord.Client):
  85. async def close(self):
  86. global openai_client
  87. if openai_client is not None:
  88. await openai_client.close()
  89. openai_client = None
  90. await super().close()
  91. # Initialiser le client Discord avec les intents modifiés
  92. client_discord = MyDiscordClient(intents=intents)
  93. # Appeler la fonction pour charger l'historique au démarrage
  94. load_conversation_history()
  95. def resize_image(image_bytes, mode='high', attachment_filename=None):
  96. try:
  97. with Image.open(BytesIO(image_bytes)) as img:
  98. original_format = img.format # Store the original format
  99. if mode == 'high':
  100. # Redimensionner pour le mode haute fidélité
  101. img.thumbnail((2000, 2000))
  102. if min(img.size) < 768:
  103. scale = 768 / min(img.size)
  104. new_size = tuple(int(x * scale) for x in img.size)
  105. img = img.resize(new_size, Image.Resampling.LANCZOS)
  106. elif mode == 'low':
  107. # Redimensionner pour le mode basse fidélité
  108. img = img.resize((512, 512))
  109. buffer = BytesIO()
  110. img_format = img.format
  111. if not img_format:
  112. if attachment_filename:
  113. _, ext = os.path.splitext(attachment_filename)
  114. ext = ext.lower()
  115. format_mapping = {
  116. '.jpg': 'JPEG',
  117. '.jpeg': 'JPEG',
  118. '.png': 'PNG',
  119. '.gif': 'GIF',
  120. '.bmp': 'BMP',
  121. '.tiff': 'TIFF'
  122. }
  123. img_format = format_mapping.get(ext, 'PNG')
  124. else:
  125. img_format = 'PNG'
  126. img.save(buffer, format=img_format)
  127. return buffer.getvalue()
  128. except Exception as e:
  129. logger.error(f"Error resizing image: {e}")
  130. raise
  131. def extract_text_from_message(message):
  132. content = message.get("content", "")
  133. if isinstance(content, list):
  134. # Extraire le texte de chaque élément de la liste
  135. texts = []
  136. for part in content:
  137. if isinstance(part, dict):
  138. text = part.get("text", "")
  139. if text:
  140. texts.append(text)
  141. return ' '.join(texts)
  142. elif isinstance(content, str):
  143. return content
  144. else:
  145. return ""
  146. def calculate_cost(usage, model='gpt-4o-mini'):
  147. input_tokens = usage.get('prompt_tokens', 0)
  148. output_tokens = usage.get('completion_tokens', 0)
  149. # Définir les tarifs par modèle
  150. model_costs = {
  151. 'gpt-4o': {
  152. 'input_rate': 5.00 / 1_000_000, # 5$ pour 1M tokens d'entrée
  153. 'output_rate': 15.00 / 1_000_000 # 15$ pour 1M tokens de sortie
  154. },
  155. 'gpt-4o-mini': {
  156. 'input_rate': 0.150 / 1_000_000, # 0.150$ pour 1M tokens d'entrée
  157. 'output_rate': 0.600 / 1_000_000 # 0.600$ pour 1M tokens de sortie
  158. }
  159. }
  160. # Obtenir les tarifs du modèle spécifié
  161. if model not in model_costs:
  162. logger.warning(f"Modèle inconnu '{model}'. Utilisation des tarifs par défaut pour 'gpt-4o-mini'.")
  163. model = 'gpt-4o-mini'
  164. input_rate = model_costs[model]['input_rate']
  165. output_rate = model_costs[model]['output_rate']
  166. # Calculer les coûts
  167. input_cost = input_tokens * input_rate
  168. output_cost = output_tokens * output_rate
  169. total_cost = input_cost + output_cost
  170. return input_tokens, output_tokens, total_cost
  171. def is_relevant_message(message):
  172. content = message["content"]
  173. if isinstance(content, list):
  174. content = ''.join(part.get('text', '') for part in content if 'text' in part)
  175. if len(content.strip()) < 5:
  176. return False
  177. discord_emoji_pattern = r'<a?:\w+:\d+>'
  178. def is_discord_emoji(part):
  179. return bool(re.fullmatch(discord_emoji_pattern, part))
  180. tokens = re.split(discord_emoji_pattern, content)
  181. emojis_only = True
  182. standard_emojis = [char for char in content if emoji.is_emoji(char)]
  183. discord_emojis = re.findall(discord_emoji_pattern, content)
  184. text_without_emojis = re.sub(discord_emoji_pattern, '', content)
  185. for char in text_without_emojis:
  186. if not char.isspace() and not emoji.is_emoji(char):
  187. emojis_only = False
  188. break
  189. if len(standard_emojis) + len(discord_emojis) == 0:
  190. emojis_only = False
  191. if emojis_only and len(content.strip()) > 0:
  192. return False
  193. return True
  194. async def call_gpt4o_for_image_analysis(image_data, user_text=None, detail='high'):
  195. try:
  196. # Préparer la requête pour GPT-4o
  197. if user_text:
  198. prompt = (
  199. f"Tu es un styliste professionnel spécialisé dans l'analyse de la silhouette et des vêtements. "
  200. f"Analyse cette image de manière extrêmement précise en tenant compte de la description suivante : \"{user_text}\". "
  201. "Si des personnages sont présents, décris-les de A à Z, des pieds à la tête. "
  202. "Mentionne leurs vêtements, accessoires, coiffure, couleur de peau, traits du visage, leur posture, et tout autre détail physique visible. "
  203. "Inclut également une estimation générale de leurs mensurations, comme la taille, la corpulence, et autres attributs physiques visibles qui pourraient influencer la conception des vêtements."
  204. )
  205. else:
  206. prompt = (
  207. "Tu es un styliste professionnel spécialisé dans l'analyse de la silhouette et des vêtements. "
  208. "Analyse cette image de manière extrêmement précise s'il te plaît. "
  209. "Si des personnages sont présents, décris-les de A à Z, des pieds à la tête. "
  210. "Mentionne leurs vêtements, accessoires, coiffure, couleur de peau, traits du visage, leur posture, et tout autre détail physique visible. "
  211. "Inclut également une estimation générale de leurs mensurations, comme la taille, la corpulence, et autres attributs physiques visibles qui pourraient influencer la conception des vêtements."
  212. )
  213. message_to_send = {
  214. "role": "user",
  215. "content": [
  216. {"type": "text", "text": prompt},
  217. {
  218. "type": "image_url",
  219. "image_url": {
  220. "url": f"data:image/jpeg;base64,{image_data}",
  221. "detail": detail
  222. }
  223. }
  224. ]
  225. }
  226. # Appel à GPT-4o
  227. response = await openai_client.chat.completions.create(
  228. model="gpt-4o",
  229. messages=[message_to_send],
  230. max_tokens=4096
  231. )
  232. if response:
  233. analysis = response.choices[0].message.content
  234. logging.info(f"Analyse de l'image par GPT-4o : {analysis}")
  235. # Calcul et affichage du coût
  236. if hasattr(response, 'usage') and response.usage:
  237. usage = {
  238. 'prompt_tokens': response.usage.prompt_tokens,
  239. 'completion_tokens': response.usage.completion_tokens
  240. }
  241. input_tokens, output_tokens, total_cost = calculate_cost(usage, model='gpt-4o')
  242. logging.info(f"Coût de l'analyse de l'image : ${total_cost:.4f} / Input: {input_tokens} / Output: {output_tokens}")
  243. else:
  244. logging.warning("Informations d'utilisation non disponibles pour le calcul du coût.")
  245. return analysis
  246. else:
  247. return None
  248. except OpenAIError as e:
  249. logger.error(f"Erreur lors de l'analyse de l'image avec GPT-4o: {e}")
  250. return None
  251. async def remove_old_image_analyses():
  252. global conversation_history
  253. max_messages_after = 6 # Nombre maximum de messages après une analyse d'image
  254. # Parcourir l'historique en identifiant les analyses d'images
  255. indices_to_remove = []
  256. for idx, msg in enumerate(conversation_history):
  257. if msg.get("role") == "system" and msg.get("content", "").startswith("Analyse de l'image :"):
  258. # Calculer le nombre de messages après ce message
  259. messages_after = len(conversation_history) - idx - 1
  260. if messages_after > max_messages_after:
  261. indices_to_remove.append(idx)
  262. # Supprimer les analyses d'images identifiées en commençant par la fin pour éviter les décalages d'indices
  263. for idx in reversed(indices_to_remove):
  264. removed_msg = conversation_history.pop(idx)
  265. logger.info(f"Analyse d'image supprimée de l'historique : {removed_msg.get('content')[:50]}...")
  266. if indices_to_remove:
  267. save_conversation_history()
  268. async def call_gpt4o_mini_with_analysis(analysis_text, user_name, user_question, has_text):
  269. try:
  270. # Préparer le message avec le prompt de personnalité et l'analyse
  271. messages = [
  272. {"role": "system", "content": PERSONALITY_PROMPT},
  273. {
  274. "role": "system",
  275. "content": f"L'analyse de l'image fournie est la suivante :\n{analysis_text}\n\n"
  276. }
  277. ]
  278. if has_text:
  279. # Préparer le message utilisateur avec le texte
  280. user_message = {
  281. "role": "user",
  282. "content": (
  283. f"{user_name} a écrit : '{user_question}'.\n"
  284. "Réponds en te basant sur l'analyse, avec ta personnalité. "
  285. "Ne mentionne pas explicitement que l'analyse est pré-existante, fais comme si tu l'avais faite toi-même."
  286. )
  287. }
  288. else:
  289. # Préparer une instruction pour commenter l'image sans texte
  290. user_message = {
  291. "role": "user",
  292. "content": (
  293. f"{user_name} a partagé une image sans texte additionnel.\n"
  294. "Commente l'image en te basant sur l'analyse, avec ta personnalité. "
  295. "Ne mentionne pas que l'analyse a été fournie à l'avance, réagis comme si tu l'avais toi-même effectuée."
  296. )
  297. }
  298. # Inclure l'historique de conversation avant d'ajouter le message utilisateur
  299. messages += conversation_history
  300. messages.append(user_message)
  301. # Appel à GPT-4o Mini pour répondre
  302. response = await openai_client.chat.completions.create(
  303. model="gpt-4o-mini",
  304. messages=messages,
  305. max_tokens=450
  306. )
  307. if response:
  308. reply = response.choices[0].message.content
  309. logging.info(f"Réponse de GPT-4o Mini : {reply}")
  310. return reply
  311. else:
  312. return None
  313. except OpenAIError as e:
  314. logger.error(f"Erreur lors de la génération de réponse avec GPT-4o Mini: {e}")
  315. return None
  316. async def read_text_file(attachment):
  317. file_bytes = await attachment.read()
  318. return file_bytes.decode('utf-8')
  319. async def encode_image_from_attachment(attachment, mode='high'):
  320. image_data = await attachment.read()
  321. resized_image = resize_image(image_data, mode=mode, attachment_filename=attachment.filename)
  322. return base64.b64encode(resized_image).decode('utf-8')
  323. async def call_openai_api(user_text, user_name, image_data=None, detail='high'):
  324. # Préparer le contenu pour l'appel API
  325. message_to_send = {
  326. "role": "user",
  327. "content": [
  328. {"type": "text", "text": f"{user_name} dit : {user_text}"}
  329. ]
  330. }
  331. # Inclure l'image dans l'appel API courant
  332. if image_data:
  333. message_to_send["content"].append({
  334. "type": "image_url",
  335. "image_url": {
  336. "url": f"data:image/jpeg;base64,{image_data}",
  337. "detail": detail
  338. }
  339. })
  340. # Assembler les messages avec le prompt de personnalité en premier
  341. messages = [
  342. {"role": "system", "content": PERSONALITY_PROMPT}
  343. ] + conversation_history + [message_to_send]
  344. try:
  345. response = await openai_client.chat.completions.create(
  346. model="gpt-4o-mini",
  347. messages=messages,
  348. max_tokens=400,
  349. temperature=1.0
  350. )
  351. if response:
  352. reply = response.choices[0].message.content
  353. # Ajouter le message de l'utilisateur à l'historique global, mais uniquement s'il ne s'agit pas d'une image
  354. if image_data is None:
  355. await add_to_conversation_history(message_to_send)
  356. # Ajouter la réponse de l'IA directement à l'historique
  357. await add_to_conversation_history({
  358. "role": "assistant",
  359. "content": reply
  360. })
  361. if hasattr(response, 'usage') and response.usage:
  362. usage = response.usage
  363. input_tokens, output_tokens, total_cost = calculate_cost({
  364. 'prompt_tokens': usage.prompt_tokens,
  365. 'completion_tokens': usage.completion_tokens
  366. })
  367. # Afficher dans la console
  368. logging.info(f"Coût de la réponse : ${total_cost:.4f} / Input: {input_tokens} / Output: {output_tokens} / Total: {input_tokens + output_tokens}")
  369. return response
  370. except OpenAIError as e:
  371. logger.error(f"Error calling OpenAI API: {e}")
  372. except Exception as e:
  373. logger.error(f"Error calling OpenAI API: {e}")
  374. return None
  375. @client_discord.event
  376. async def on_ready():
  377. logger.info(f'{BOT_NAME} connecté en tant que {client_discord.user}')
  378. if not conversation_history:
  379. logger.info("Aucun historique trouvé. L'historique commence vide.")
  380. # Envoyer un message de version dans le canal Discord
  381. channel = client_discord.get_channel(chatgpt_channel_id)
  382. if channel:
  383. try:
  384. embed = discord.Embed(
  385. title=f"Bot Démarré",
  386. description=f"🎉 {BOT_NAME} est en ligne ! Version {BOT_VERSION}",
  387. color=0x00ff00 # Vert
  388. )
  389. await channel.send(embed=embed)
  390. logger.info(f"Message de connexion envoyé dans le canal ID {chatgpt_channel_id}")
  391. except discord.Forbidden:
  392. logger.error(f"Permissions insuffisantes pour envoyer des messages dans le canal ID {chatgpt_channel_id}.")
  393. except discord.HTTPException as e:
  394. logger.error(f"Erreur lors de l'envoi du message de connexion : {e}")
  395. else:
  396. logger.error(f"Canal avec ID {chatgpt_channel_id} non trouvé.")
  397. @client_discord.event
  398. async def on_message(message):
  399. global conversation_history
  400. # Vérifier si le message provient du canal autorisé
  401. if message.channel.id != chatgpt_channel_id:
  402. return
  403. # Ignorer les messages du bot lui-même
  404. if message.author == client_discord.user:
  405. return
  406. user_text = message.content.strip()
  407. image_data = None
  408. file_content = None
  409. attachment_filename = None
  410. # Vérifier si le message est la commande de réinitialisation
  411. if user_text.lower() == "!reset_history":
  412. # Vérifier si l'utilisateur a les permissions administratives
  413. if not message.author.guild_permissions.administrator:
  414. await message.channel.send("❌ Vous n'avez pas la permission d'utiliser cette commande.")
  415. return
  416. conversation_history = []
  417. save_conversation_history()
  418. await message.channel.send("✅ L'historique des conversations a été réinitialisé.")
  419. logger.info(f"Historique des conversations réinitialisé par {message.author}.")
  420. return # Arrêter le traitement du message après la réinitialisation
  421. # Extensions de fichiers autorisées
  422. allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
  423. # Variables pour stocker si le message contient une image et/ou un fichier
  424. has_image = False
  425. has_file = False
  426. # Vérifier s'il y a une pièce jointe
  427. if message.attachments:
  428. for attachment in message.attachments:
  429. # Vérifier si c'est un fichier avec une extension autorisée
  430. if any(attachment.filename.endswith(ext) for ext in allowed_extensions):
  431. file_content = await read_text_file(attachment)
  432. attachment_filename = attachment.filename
  433. break
  434. # Vérifier si c'est une image
  435. elif attachment.content_type in ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff']:
  436. image_data = await encode_image_from_attachment(attachment, mode='high')
  437. break
  438. # Si une image est présente, la traiter
  439. if image_data:
  440. has_user_text = has_text(user_text)
  441. user_text_to_use = user_text if has_user_text else None
  442. # **Étape 1 : Envoyer un message temporaire indiquant que l'image est en cours d'analyse**
  443. temp_msg = await message.channel.send(f"*{BOT_NAME} observe l'image...*")
  444. try:
  445. # Étape 2 : GPT-4o analyse l'image, potentiellement guidée par le texte de l'utilisateur
  446. analysis = await call_gpt4o_for_image_analysis(image_data, user_text=user_text_to_use)
  447. if analysis:
  448. # **Ajouter l'analyse à l'historique avant de réagir avec GPT-4o Mini**
  449. analysis_message = {
  450. "role": "system",
  451. "content": f"Analyse de l'image : {analysis}"
  452. }
  453. await add_to_conversation_history(analysis_message)
  454. # Étape 3 : GPT-4o Mini réagit à la question et à l'analyse
  455. reply = await call_gpt4o_mini_with_analysis(analysis, message.author.name, user_text, has_user_text)
  456. if reply:
  457. # **Étape 4 : Supprimer le message temporaire**
  458. await temp_msg.delete()
  459. # **Étape 5 : Envoyer la réponse finale**
  460. await message.channel.send(reply)
  461. # **Ajout des messages à l'historique**
  462. # Créer un message utilisateur modifié indiquant qu'une image a été postée
  463. if has_user_text:
  464. user_message_content = f"{user_text} (a posté une image.)"
  465. else:
  466. user_message_content = (
  467. "Une image a été postée, mais elle n'est pas disponible pour analyse directe. "
  468. "Veuillez vous baser uniquement sur l'analyse fournie."
  469. )
  470. user_message = {
  471. "role": "user",
  472. "content": user_message_content
  473. }
  474. # Ajouter le message utilisateur à l'historique
  475. await add_to_conversation_history(user_message)
  476. # Créer le message assistant avec la réponse de GPT-4o Mini
  477. assistant_message = {
  478. "role": "assistant",
  479. "content": reply
  480. }
  481. # Ajouter le message assistant à l'historique
  482. await add_to_conversation_history(assistant_message)
  483. else:
  484. # **Étape 4 : Supprimer le message temporaire en cas d'échec de génération de réponse**
  485. await temp_msg.delete()
  486. await message.channel.send("Désolé, je n'ai pas pu générer une réponse.")
  487. else:
  488. # **Étape 4 : Supprimer le message temporaire en cas d'échec d'analyse**
  489. await temp_msg.delete()
  490. await message.channel.send("Désolé, je n'ai pas pu analyser l'image.")
  491. except Exception as e:
  492. # **Étape 4 : Supprimer le message temporaire en cas d'erreur**
  493. await temp_msg.delete()
  494. await message.channel.send("Une erreur est survenue lors du traitement de l'image.")
  495. logger.error(f"Error during image processing: {e}")
  496. # Après traitement de l'image, ne pas continuer
  497. return
  498. # Ajouter le contenu du fichier à la requête si présent
  499. if file_content:
  500. user_text += f"\nContenu du fichier {attachment.filename}:\n{file_content}"
  501. # Vérifier si le texte n'est pas vide après ajout du contenu du fichier
  502. if not has_text(user_text):
  503. return # Ne pas appeler l'API si le texte est vide
  504. # Appeler l'API OpenAI
  505. result = await call_openai_api(user_text, message.author.name, image_data)
  506. if result:
  507. reply = result.choices[0].message.content
  508. await message.channel.send(reply)
  509. async def add_to_conversation_history(new_message):
  510. global conversation_history
  511. # Ne pas ajouter le PERSONALITY_PROMPT à l'historique
  512. if new_message.get("role") == "system" and new_message.get("content") == PERSONALITY_PROMPT:
  513. logger.debug("PERSONALITY_PROMPT système non ajouté à l'historique.")
  514. return
  515. # Filtrer les messages pertinents pour l'historique
  516. if is_relevant_message(new_message):
  517. # Ajouter le message à l'historique
  518. conversation_history.append(new_message)
  519. save_conversation_history()
  520. logger.debug(f"Message ajouté à l'historique. Taille actuelle : {len(conversation_history)}")
  521. # Gérer la suppression des analyses d'images après 6 messages
  522. await remove_old_image_analyses()
  523. # Vérifier si la limite de 150 messages est atteinte
  524. if len(conversation_history) > 150:
  525. logger.info("Limite de 150 messages atteinte.")
  526. # Calculer combien de messages doivent être supprimés
  527. excess_messages = len(conversation_history) - 150
  528. if excess_messages > 0:
  529. # Supprimer les messages les plus anciens
  530. del conversation_history[:excess_messages]
  531. save_conversation_history()
  532. logger.info(f"{excess_messages} messages les plus anciens ont été supprimés pour maintenir l'historique à 150 messages.")
  533. # Démarrer le bot Discord
  534. client_discord.run(DISCORD_TOKEN)