chatbot.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  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.1"
  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"Analyse cette image de manière extrêmement précise en tenant compte de la description suivante : \"{user_text}\"."
  200. "Si des personnages sont présents, décris avec détails leurs vêtements, accessoires et physique."
  201. "Décris leurs courbes, leur taille, leur poids, leurs mensurations."
  202. "Décris comment ils intéragissent avec leur environnement et avec les autres personnages."
  203. )
  204. else:
  205. prompt = (
  206. "Analyse cette image de manière extrêmement précise s'il te plaît."
  207. "Si des personnages sont présents, décris avec détails leurs vêtements, accessoires et physique."
  208. "Décris leurs courbes, leur taille, leur poids, leurs mensurations."
  209. "Décris comment ils intéragissent avec leur environnement et avec les autres personnages."
  210. )
  211. message_to_send = {
  212. "role": "user",
  213. "content": [
  214. {"type": "text", "text": prompt},
  215. {
  216. "type": "image_url",
  217. "image_url": {
  218. "url": f"data:image/jpeg;base64,{image_data}",
  219. "detail": detail
  220. }
  221. }
  222. ]
  223. }
  224. # Appel à GPT-4o
  225. response = await openai_client.chat.completions.create(
  226. model="gpt-4o",
  227. messages=[message_to_send],
  228. max_tokens=4096
  229. )
  230. if response:
  231. analysis = response.choices[0].message.content
  232. logging.info(f"Analyse de l'image par GPT-4o : {analysis}")
  233. # Calcul et affichage du coût
  234. if hasattr(response, 'usage') and response.usage:
  235. usage = {
  236. 'prompt_tokens': response.usage.prompt_tokens,
  237. 'completion_tokens': response.usage.completion_tokens
  238. }
  239. input_tokens, output_tokens, total_cost = calculate_cost(usage, model='gpt-4o')
  240. logging.info(f"Coût de l'analyse de l'image : ${total_cost:.4f} / Input: {input_tokens} / Output: {output_tokens}")
  241. else:
  242. logging.warning("Informations d'utilisation non disponibles pour le calcul du coût.")
  243. return analysis
  244. else:
  245. return None
  246. except OpenAIError as e:
  247. logger.error(f"Erreur lors de l'analyse de l'image avec GPT-4o: {e}")
  248. return None
  249. async def remove_old_image_analyses():
  250. global conversation_history
  251. max_messages_after = 6 # Nombre maximum de messages après une analyse d'image
  252. # Parcourir l'historique en identifiant les analyses d'images
  253. indices_to_remove = []
  254. for idx, msg in enumerate(conversation_history):
  255. if msg.get("role") == "system" and msg.get("content", "").startswith("Analyse de l'image :"):
  256. # Calculer le nombre de messages après ce message
  257. messages_after = len(conversation_history) - idx - 1
  258. if messages_after > max_messages_after:
  259. indices_to_remove.append(idx)
  260. # Supprimer les analyses d'images identifiées en commençant par la fin pour éviter les décalages d'indices
  261. for idx in reversed(indices_to_remove):
  262. removed_msg = conversation_history.pop(idx)
  263. logger.info(f"Analyse d'image supprimée de l'historique : {removed_msg.get('content')[:50]}...")
  264. if indices_to_remove:
  265. save_conversation_history()
  266. async def call_gpt4o_mini_with_analysis(analysis_text, user_name, user_question, has_text):
  267. try:
  268. # Préparer le message avec le prompt de personnalité et l'analyse
  269. messages = [
  270. {"role": "system", "content": PERSONALITY_PROMPT},
  271. {
  272. "role": "system",
  273. "content": f"L'analyse de l'image fournie est la suivante :\n{analysis_text}\n\n"
  274. }
  275. ]
  276. if has_text:
  277. # Préparer le message utilisateur avec le texte
  278. user_message = {
  279. "role": "user",
  280. "content": (
  281. f"{user_name} a écrit : '{user_question}'.\n"
  282. "Réponds en te basant sur l'analyse, avec ta personnalité. "
  283. "Ne mentionne pas explicitement que l'analyse est pré-existante, fais comme si tu l'avais faite toi-même."
  284. )
  285. }
  286. else:
  287. # Préparer une instruction pour commenter l'image sans texte
  288. user_message = {
  289. "role": "user",
  290. "content": (
  291. f"{user_name} a partagé une image sans texte additionnel.\n"
  292. "Commente l'image en te basant sur l'analyse, avec ta personnalité. "
  293. "Ne mentionne pas que l'analyse a été fournie à l'avance, réagis comme si tu l'avais toi-même effectuée."
  294. )
  295. }
  296. # Inclure l'historique de conversation avant d'ajouter le message utilisateur
  297. messages += conversation_history
  298. messages.append(user_message)
  299. # Appel à GPT-4o Mini pour répondre
  300. response = await openai_client.chat.completions.create(
  301. model="gpt-4o-mini",
  302. messages=messages,
  303. max_tokens=450
  304. )
  305. if response:
  306. reply = response.choices[0].message.content
  307. logging.info(f"Réponse de GPT-4o Mini : {reply}")
  308. return reply
  309. else:
  310. return None
  311. except OpenAIError as e:
  312. logger.error(f"Erreur lors de la génération de réponse avec GPT-4o Mini: {e}")
  313. return None
  314. async def read_text_file(attachment):
  315. file_bytes = await attachment.read()
  316. return file_bytes.decode('utf-8')
  317. async def encode_image_from_attachment(attachment, mode='high'):
  318. image_data = await attachment.read()
  319. resized_image = resize_image(image_data, mode=mode, attachment_filename=attachment.filename)
  320. return base64.b64encode(resized_image).decode('utf-8')
  321. async def call_openai_api(user_text, user_name, image_data=None, detail='high'):
  322. # Préparer le contenu pour l'appel API
  323. message_to_send = {
  324. "role": "user",
  325. "content": [
  326. {"type": "text", "text": f"{user_name} dit : {user_text}"}
  327. ]
  328. }
  329. # Inclure l'image dans l'appel API courant
  330. if image_data:
  331. message_to_send["content"].append({
  332. "type": "image_url",
  333. "image_url": {
  334. "url": f"data:image/jpeg;base64,{image_data}",
  335. "detail": detail
  336. }
  337. })
  338. # Assembler les messages avec le prompt de personnalité en premier
  339. messages = [
  340. {"role": "system", "content": PERSONALITY_PROMPT}
  341. ] + conversation_history + [message_to_send]
  342. try:
  343. response = await openai_client.chat.completions.create(
  344. model="gpt-4o-mini",
  345. messages=messages,
  346. max_tokens=400,
  347. temperature=1.0
  348. )
  349. if response:
  350. reply = response.choices[0].message.content
  351. # Ajouter le message de l'utilisateur à l'historique global, mais uniquement s'il ne s'agit pas d'une image
  352. if image_data is None:
  353. await add_to_conversation_history(message_to_send)
  354. # Ajouter la réponse de l'IA directement à l'historique
  355. await add_to_conversation_history({
  356. "role": "assistant",
  357. "content": reply
  358. })
  359. if hasattr(response, 'usage') and response.usage:
  360. usage = response.usage
  361. input_tokens, output_tokens, total_cost = calculate_cost({
  362. 'prompt_tokens': usage.prompt_tokens,
  363. 'completion_tokens': usage.completion_tokens
  364. })
  365. # Afficher dans la console
  366. logging.info(f"Coût de la réponse : ${total_cost:.4f} / Input: {input_tokens} / Output: {output_tokens} / Total: {input_tokens + output_tokens}")
  367. return response
  368. except OpenAIError as e:
  369. logger.error(f"Error calling OpenAI API: {e}")
  370. except Exception as e:
  371. logger.error(f"Error calling OpenAI API: {e}")
  372. return None
  373. @client_discord.event
  374. async def on_ready():
  375. logger.info(f'{BOT_NAME} connecté en tant que {client_discord.user}')
  376. if not conversation_history:
  377. logger.info("Aucun historique trouvé. L'historique commence vide.")
  378. # Envoyer un message de version dans le canal Discord
  379. channel = client_discord.get_channel(chatgpt_channel_id)
  380. if channel:
  381. try:
  382. embed = discord.Embed(
  383. title=f"Bot Démarré",
  384. description=f"🎉 {BOT_NAME} est en ligne ! Version {BOT_VERSION}",
  385. color=0x00ff00 # Vert
  386. )
  387. await channel.send(embed=embed)
  388. logger.info(f"Message de connexion envoyé dans le canal ID {chatgpt_channel_id}")
  389. except discord.Forbidden:
  390. logger.error(f"Permissions insuffisantes pour envoyer des messages dans le canal ID {chatgpt_channel_id}.")
  391. except discord.HTTPException as e:
  392. logger.error(f"Erreur lors de l'envoi du message de connexion : {e}")
  393. else:
  394. logger.error(f"Canal avec ID {chatgpt_channel_id} non trouvé.")
  395. @client_discord.event
  396. async def on_message(message):
  397. global conversation_history
  398. # Vérifier si le message provient du canal autorisé
  399. if message.channel.id != chatgpt_channel_id:
  400. return
  401. # Ignorer les messages du bot lui-même
  402. if message.author == client_discord.user:
  403. return
  404. user_text = message.content.strip()
  405. image_data = None
  406. file_content = None
  407. attachment_filename = None
  408. # Vérifier si le message est la commande de réinitialisation
  409. if user_text.lower() == "!reset_history":
  410. # Vérifier si l'utilisateur a les permissions administratives
  411. if not message.author.guild_permissions.administrator:
  412. await message.channel.send("❌ Vous n'avez pas la permission d'utiliser cette commande.")
  413. return
  414. conversation_history = []
  415. save_conversation_history()
  416. await message.channel.send("✅ L'historique des conversations a été réinitialisé.")
  417. logger.info(f"Historique des conversations réinitialisé par {message.author}.")
  418. return # Arrêter le traitement du message après la réinitialisation
  419. # Extensions de fichiers autorisées
  420. allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
  421. # Variables pour stocker si le message contient une image et/ou un fichier
  422. has_image = False
  423. has_file = False
  424. # Vérifier s'il y a une pièce jointe
  425. if message.attachments:
  426. for attachment in message.attachments:
  427. # Vérifier si c'est un fichier avec une extension autorisée
  428. if any(attachment.filename.endswith(ext) for ext in allowed_extensions):
  429. file_content = await read_text_file(attachment)
  430. attachment_filename = attachment.filename
  431. break
  432. # Vérifier si c'est une image
  433. elif attachment.content_type in ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff']:
  434. image_data = await encode_image_from_attachment(attachment, mode='high')
  435. break
  436. # Si une image est présente, la traiter
  437. if image_data:
  438. has_user_text = has_text(user_text)
  439. user_text_to_use = user_text if has_user_text else None
  440. # **Étape 1 : Envoyer un message temporaire indiquant que l'image est en cours d'analyse**
  441. temp_msg = await message.channel.send(f"*{BOT_NAME} observe l'image...*")
  442. try:
  443. # Étape 2 : GPT-4o analyse l'image, potentiellement guidée par le texte de l'utilisateur
  444. analysis = await call_gpt4o_for_image_analysis(image_data, user_text=user_text_to_use)
  445. if analysis:
  446. # **Ajouter l'analyse à l'historique avant de réagir avec GPT-4o Mini**
  447. analysis_message = {
  448. "role": "system",
  449. "content": f"Analyse de l'image : {analysis}"
  450. }
  451. await add_to_conversation_history(analysis_message)
  452. # Étape 3 : GPT-4o Mini réagit à la question et à l'analyse
  453. reply = await call_gpt4o_mini_with_analysis(analysis, message.author.name, user_text, has_user_text)
  454. if reply:
  455. # **Étape 4 : Supprimer le message temporaire**
  456. await temp_msg.delete()
  457. # **Étape 5 : Envoyer la réponse finale**
  458. await message.channel.send(reply)
  459. # **Ajout des messages à l'historique**
  460. # Créer un message utilisateur modifié indiquant qu'une image a été postée
  461. if has_user_text:
  462. user_message_content = f"{user_text} (a posté une image.)"
  463. else:
  464. user_message_content = (
  465. "Une image a été postée, mais elle n'est pas disponible pour analyse directe. "
  466. "Veuillez vous baser uniquement sur l'analyse fournie."
  467. )
  468. user_message = {
  469. "role": "user",
  470. "content": user_message_content
  471. }
  472. # Ajouter le message utilisateur à l'historique
  473. await add_to_conversation_history(user_message)
  474. # Créer le message assistant avec la réponse de GPT-4o Mini
  475. assistant_message = {
  476. "role": "assistant",
  477. "content": reply
  478. }
  479. # Ajouter le message assistant à l'historique
  480. await add_to_conversation_history(assistant_message)
  481. else:
  482. # **Étape 4 : Supprimer le message temporaire en cas d'échec de génération de réponse**
  483. await temp_msg.delete()
  484. await message.channel.send("Désolé, je n'ai pas pu générer une réponse.")
  485. else:
  486. # **Étape 4 : Supprimer le message temporaire en cas d'échec d'analyse**
  487. await temp_msg.delete()
  488. await message.channel.send("Désolé, je n'ai pas pu analyser l'image.")
  489. except Exception as e:
  490. # **Étape 4 : Supprimer le message temporaire en cas d'erreur**
  491. await temp_msg.delete()
  492. await message.channel.send("Une erreur est survenue lors du traitement de l'image.")
  493. logger.error(f"Error during image processing: {e}")
  494. # Après traitement de l'image, ne pas continuer
  495. return
  496. # Ajouter le contenu du fichier à la requête si présent
  497. if file_content:
  498. user_text += f"\nContenu du fichier {attachment.filename}:\n{file_content}"
  499. # Vérifier si le texte n'est pas vide après ajout du contenu du fichier
  500. if not has_text(user_text):
  501. return # Ne pas appeler l'API si le texte est vide
  502. # Appeler l'API OpenAI
  503. result = await call_openai_api(user_text, message.author.name, image_data)
  504. if result:
  505. reply = result.choices[0].message.content
  506. await message.channel.send(reply)
  507. async def add_to_conversation_history(new_message):
  508. global conversation_history
  509. # Ne pas ajouter le PERSONALITY_PROMPT à l'historique
  510. if new_message.get("role") == "system" and new_message.get("content") == PERSONALITY_PROMPT:
  511. logger.debug("PERSONALITY_PROMPT système non ajouté à l'historique.")
  512. return
  513. # Filtrer les messages pertinents pour l'historique
  514. if is_relevant_message(new_message):
  515. # Ajouter le message à l'historique
  516. conversation_history.append(new_message)
  517. save_conversation_history()
  518. logger.debug(f"Message ajouté à l'historique. Taille actuelle : {len(conversation_history)}")
  519. # Gérer la suppression des analyses d'images après 6 messages
  520. await remove_old_image_analyses()
  521. # Vérifier si la limite de 150 messages est atteinte
  522. if len(conversation_history) > 150:
  523. logger.info("Limite de 150 messages atteinte.")
  524. # Calculer combien de messages doivent être supprimés
  525. excess_messages = len(conversation_history) - 150
  526. if excess_messages > 0:
  527. # Supprimer les messages les plus anciens
  528. del conversation_history[:excess_messages]
  529. save_conversation_history()
  530. logger.info(f"{excess_messages} messages les plus anciens ont été supprimés pour maintenir l'historique à 150 messages.")
  531. # Démarrer le bot Discord
  532. client_discord.run(DISCORD_TOKEN)