1
0

chatbot.py 26 KB

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