1
0

chatbot.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. import os
  2. import mysql.connector
  3. from mysql.connector import Error
  4. import base64
  5. import json
  6. import logging
  7. import re
  8. from io import BytesIO
  9. import discord
  10. from discord.ext import commands
  11. from dotenv import load_dotenv
  12. from PIL import Image
  13. import tiktoken
  14. from openai import AsyncOpenAI, OpenAIError
  15. # ================================
  16. # Configuration et Initialisation
  17. # ================================
  18. # Charger les variables d'environnement depuis le fichier .env
  19. load_dotenv()
  20. DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
  21. OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
  22. DISCORD_CHANNEL_ID = os.getenv('DISCORD_CHANNEL_ID')
  23. PERSONALITY_PROMPT_FILE = os.getenv('PERSONALITY_PROMPT_FILE', 'personality_prompt.txt')
  24. CONVERSATION_HISTORY_FILE = os.getenv('CONVERSATION_HISTORY_FILE', 'conversation_history.json')
  25. BOT_NAME = os.getenv('BOT_NAME', 'ChatBot')
  26. BOT_VERSION = "2.6.0"
  27. # Validation des variables d'environnement
  28. required_env_vars = {
  29. 'DISCORD_TOKEN': DISCORD_TOKEN,
  30. 'OPENAI_API_KEY': OPENAI_API_KEY,
  31. 'DISCORD_CHANNEL_ID': DISCORD_CHANNEL_ID
  32. }
  33. missing_vars = [var for var, val in required_env_vars.items() if val is None]
  34. if missing_vars:
  35. raise ValueError(f"Les variables d'environnement suivantes ne sont pas définies: {', '.join(missing_vars)}")
  36. # Vérification de l'existence du fichier de prompt de personnalité
  37. if not os.path.isfile(PERSONALITY_PROMPT_FILE):
  38. raise FileNotFoundError(f"Le fichier de prompt de personnalité '{PERSONALITY_PROMPT_FILE}' est introuvable.")
  39. # Lire le prompt de personnalité depuis le fichier
  40. with open(PERSONALITY_PROMPT_FILE, 'r', encoding='utf-8') as f:
  41. PERSONALITY_PROMPT = f.read().strip()
  42. # Initialiser le client OpenAI asynchrone
  43. openai_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
  44. # Configurer les logs
  45. LOG_FORMAT = '%(asctime)s : %(name)s : %(levelname)s : %(message)s'
  46. logging.basicConfig(
  47. handlers=[
  48. logging.FileHandler("./chatbot.log", mode='a', encoding='utf-8'),
  49. logging.StreamHandler()
  50. ],
  51. format=LOG_FORMAT,
  52. level=logging.INFO
  53. )
  54. logger = logging.getLogger(BOT_NAME)
  55. logger.setLevel(logging.INFO)
  56. # Réduire le niveau de log pour certaines librairies
  57. logging.getLogger('httpx').setLevel(logging.WARNING)
  58. # Initialiser les intents Discord
  59. intents = discord.Intents.default()
  60. intents.message_content = True
  61. # Initialiser le client Discord
  62. class MyDiscordClient(discord.Client):
  63. def __init__(self, **options):
  64. super().__init__(**options)
  65. async def close(self):
  66. if openai_client:
  67. await openai_client.close()
  68. await super().close()
  69. client_discord = MyDiscordClient(intents=intents)
  70. # Convertir l'ID du canal Discord en entier
  71. try:
  72. chatgpt_channel_id = int(DISCORD_CHANNEL_ID)
  73. except ValueError:
  74. raise ValueError("L'ID du canal Discord est invalide. Assurez-vous qu'il s'agit d'un entier.")
  75. # ========================
  76. # Configuration de la base de données
  77. # ========================
  78. def create_db_connection():
  79. try:
  80. connection = mysql.connector.connect(
  81. host=os.getenv('DB_HOST'),
  82. user=os.getenv('DB_USER'),
  83. password=os.getenv('DB_PASSWORD'),
  84. database=os.getenv('DB_NAME'),
  85. charset='utf8mb4',
  86. collation='utf8mb4_unicode_ci'
  87. )
  88. if connection.is_connected():
  89. logger.info("Connexion réussie à MariaDB")
  90. return connection
  91. except Error as e:
  92. logger.error(f"Erreur de connexion à MariaDB: {e}")
  93. return None
  94. # ========================
  95. # Gestion du chargement et de la sauvegarde de l'Historique
  96. # ========================
  97. conversation_history = []
  98. last_analysis_index = None
  99. messages_since_last_analysis = 0
  100. def load_conversation_history(db_connection):
  101. global conversation_history
  102. try:
  103. cursor = db_connection.cursor(dictionary=True)
  104. cursor.execute("SELECT role, content FROM conversation_history ORDER BY id ASC")
  105. rows = cursor.fetchall()
  106. conversation_history = [row for row in rows if not (row['role'] == "system" and row['content'] == PERSONALITY_PROMPT)]
  107. logger.info("Historique chargé depuis la base de données")
  108. except Error as e:
  109. logger.error(f"Erreur lors du chargement de l'historique depuis la base de données: {e}")
  110. conversation_history = []
  111. finally:
  112. cursor.close()
  113. def save_message_to_db(db_connection, role, content):
  114. try:
  115. cursor = db_connection.cursor()
  116. sql = "INSERT INTO conversation_history (role, content) VALUES (%s, %s)"
  117. cursor.execute(sql, (role, json.dumps(content) if isinstance(content, (dict, list)) else content))
  118. db_connection.commit()
  119. logger.debug(f"Message sauvegardé dans la base de données: {role} - {content[:50]}...")
  120. except Error as e:
  121. logger.error(f"Erreur lors de la sauvegarde du message dans la base de données: {e}")
  122. finally:
  123. cursor.close()
  124. # ====================
  125. # Fonctions Utilitaires
  126. # ====================
  127. def has_text(text):
  128. """Détermine si le texte fourni est non vide après suppression des espaces."""
  129. return bool(text.strip())
  130. def resize_image(image_bytes, mode='high', attachment_filename=None):
  131. """Redimensionne l'image selon le mode spécifié."""
  132. try:
  133. with Image.open(BytesIO(image_bytes)) as img:
  134. original_format = img.format # Stocker le format original
  135. if mode == 'high':
  136. img.thumbnail((2000, 2000))
  137. if min(img.size) < 768:
  138. scale = 768 / min(img.size)
  139. new_size = tuple(int(x * scale) for x in img.size)
  140. img = img.resize(new_size, Image.Resampling.LANCZOS)
  141. elif mode == 'low':
  142. img = img.resize((512, 512))
  143. buffer = BytesIO()
  144. img_format = img.format or _infer_image_format(attachment_filename)
  145. img.save(buffer, format=img_format)
  146. return buffer.getvalue()
  147. except Exception as e:
  148. logger.error(f"Erreur lors du redimensionnement de l'image : {e}")
  149. raise
  150. def _infer_image_format(filename):
  151. """Déduit le format de l'image basé sur l'extension du fichier."""
  152. if filename:
  153. _, ext = os.path.splitext(filename)
  154. ext = ext.lower()
  155. format_mapping = {
  156. '.jpg': 'JPEG',
  157. '.jpeg': 'JPEG',
  158. '.png': 'PNG',
  159. '.gif': 'GIF',
  160. '.bmp': 'BMP',
  161. '.tiff': 'TIFF'
  162. }
  163. return format_mapping.get(ext, 'PNG')
  164. return 'PNG'
  165. def extract_text_from_message(message):
  166. """Extrait le texte du message."""
  167. content = message.get("content", "")
  168. if isinstance(content, list):
  169. texts = [part.get("text", "") for part in content if isinstance(part, dict) and part.get("text")]
  170. return ' '.join(texts)
  171. elif isinstance(content, str):
  172. return content
  173. return ""
  174. def calculate_cost(usage, model='gpt-4o-mini'):
  175. """Calcule le coût basé sur l'utilisation des tokens."""
  176. input_tokens = usage.get('prompt_tokens', 0)
  177. output_tokens = usage.get('completion_tokens', 0)
  178. model_costs = {
  179. 'gpt-4o': {
  180. 'input_rate': 5.00 / 1_000_000, # 5$ pour 1M tokens d'entrée
  181. 'output_rate': 15.00 / 1_000_000 # 15$ pour 1M tokens de sortie
  182. },
  183. 'gpt-4o-mini': {
  184. 'input_rate': 0.150 / 1_000_000, # 0.150$ pour 1M tokens d'entrée
  185. 'output_rate': 0.600 / 1_000_000 # 0.600$ pour 1M tokens de sortie
  186. }
  187. }
  188. rates = model_costs.get(model, model_costs['gpt-4o-mini'])
  189. input_cost = input_tokens * rates['input_rate']
  190. output_cost = output_tokens * rates['output_rate']
  191. total_cost = input_cost + output_cost
  192. if model not in model_costs:
  193. logger.warning(f"Modèle inconnu '{model}'. Utilisation des tarifs par défaut pour 'gpt-4o-mini'.")
  194. return input_tokens, output_tokens, total_cost
  195. async def read_text_file(attachment):
  196. """Lit le contenu d'un fichier texte attaché."""
  197. file_bytes = await attachment.read()
  198. return file_bytes.decode('utf-8')
  199. async def encode_image_from_attachment(attachment, mode='high'):
  200. """Encode une image depuis une pièce jointe en base64 après redimensionnement."""
  201. image_data = await attachment.read()
  202. resized_image = resize_image(image_data, mode=mode, attachment_filename=attachment.filename)
  203. return base64.b64encode(resized_image).decode('utf-8')
  204. # ========================
  205. # Interaction avec OpenAI
  206. # ========================
  207. # Charger l'encodeur pour le modèle GPT-4o mini
  208. encoding = tiktoken.get_encoding("o200k_base")
  209. async def call_openai_model(model, messages, max_tokens, temperature=0.8):
  210. """Appelle un modèle OpenAI avec les paramètres spécifiés et gère la réponse."""
  211. try:
  212. response = await openai_client.chat.completions.create(
  213. model=model,
  214. messages=messages,
  215. max_tokens=max_tokens,
  216. temperature=temperature
  217. )
  218. if response and response.choices:
  219. reply = response.choices[0].message.content
  220. # Ne pas logger les réponses de 'gpt-4o-mini' et 'gpt-4o'
  221. if model not in ["gpt-4o-mini", "gpt-4o"]:
  222. logger.info(f"Réponse de {model}: {reply[:100]}...")
  223. if hasattr(response, 'usage') and response.usage:
  224. usage = {
  225. 'prompt_tokens': response.usage.prompt_tokens,
  226. 'completion_tokens': response.usage.completion_tokens
  227. }
  228. _, _, total_cost = calculate_cost(usage, model=model)
  229. # Log avec les tokens d'entrée et de sortie
  230. logger.info(f"Coût de l'utilisation de {model}: ${total_cost:.4f} / Input: {usage['prompt_tokens']} / Output: {usage['completion_tokens']}")
  231. else:
  232. logger.warning(f"Informations d'utilisation non disponibles pour {model}.")
  233. return reply
  234. except OpenAIError as e:
  235. logger.error(f"Erreur lors de l'appel à l'API OpenAI avec {model}: {e}")
  236. except Exception as e:
  237. logger.error(f"Erreur inattendue lors de l'appel à l'API OpenAI avec {model}: {e}")
  238. return None
  239. async def call_gpt4o_for_image_analysis(image_data, user_text=None, detail='high'):
  240. """Appelle GPT-4o pour analyser une image."""
  241. prompt = (
  242. "Tu es un expert en analyse d'images et de textes, spécialisé dans l'étude du corps humain. "
  243. "On te présente une image ou un texte qui pourrait contenir des informations importantes. "
  244. "Analyse chaque détail de manière méticuleuse. "
  245. "Si l'image montre un environnement sans personnage, décris minutieusement les objets, leur disposition, les couleurs, textures, formes, et tout autre élément notable. "
  246. "Si du texte est présent, analyse chaque mot attentivement : style, mise en page, ou tout détail subtil qui pourrait en révéler plus sur le contexte ou l'intention. "
  247. "Si des personnages sont présents, décris-les avec précision, dans l'ordre suivant : D'abord leur posture et expression faciale. "
  248. "Puis leurs vêtements dans leur intégralité. "
  249. "Puis leur physique, en commençant par les cheveux, visage, épaules, poitrine, ventre, taille, hanches, fesses, cuisses, jambes et pieds."
  250. )
  251. if user_text:
  252. prompt += f" Voici ce que l'on te décrit : \"{user_text}\"."
  253. message_to_send = {
  254. "role": "user",
  255. "content": [
  256. {"type": "text", "text": prompt},
  257. {
  258. "type": "image_url",
  259. "image_url": {
  260. "url": f"data:image/jpeg;base64,{image_data}",
  261. "detail": detail
  262. }
  263. }
  264. ]
  265. }
  266. messages = [message_to_send]
  267. analysis = await call_openai_model(
  268. model="gpt-4o",
  269. messages=messages,
  270. max_tokens=4096,
  271. temperature=1.0
  272. )
  273. if analysis:
  274. logger.info(f"Analyse de l'image par GPT-4o : {analysis}")
  275. return analysis
  276. async def call_gpt4o_mini_with_analysis(analysis_text, user_name, user_question, has_text_flag):
  277. """Appelle GPT-4o Mini pour générer une réponse basée sur l'analyse de l'image."""
  278. system_messages = [
  279. {"role": "system", "content": PERSONALITY_PROMPT},
  280. {
  281. "role": "system",
  282. "content": f"L'analyse de l'image fournie est la suivante :\n{analysis_text}\n\n"
  283. }
  284. ]
  285. if has_text_flag:
  286. user_content = (
  287. f"Tu es Rika, une femme de 24 ans et membre à part entière du serveur Discord sur lequel tu te trouves. "
  288. f"{user_name} a posté un message contenant une image et a écrit avec : '{user_question}'. "
  289. "Réponds à l'utilisateur en te basant sur l'analyse, avec ta personnalité. "
  290. "Ne mentionne pas explicitement que l'analyse est pré-existante, fais comme si tu l'avais faite toi-même."
  291. )
  292. else:
  293. user_content = (
  294. f"Tu es Rika, une femme de 24 ans et membre à part entière du serveur Discord sur lequel tu te trouves. "
  295. f"{user_name} a partagé une image sans texte additionnel. "
  296. "Commente l'image en te basant sur l'analyse, avec ta personnalité. "
  297. "Ne mentionne pas que l'analyse a été fournie à l'avance, réagis comme si tu l'avais toi-même effectuée."
  298. )
  299. user_message = {"role": "user", "content": user_content}
  300. messages = system_messages + conversation_history + [user_message]
  301. reply = await call_openai_model(
  302. model="gpt-4o-mini",
  303. messages=messages,
  304. max_tokens=450,
  305. temperature=1.0
  306. )
  307. return reply
  308. async def call_openai_api(user_text, user_name, image_data=None, detail='high'):
  309. """Appelle l'API OpenAI pour générer une réponse basée sur le texte et/ou l'image."""
  310. text = f"{user_name} dit : {user_text}"
  311. if image_data:
  312. text += " (a posté une image.)"
  313. message_to_send = {
  314. "role": "user",
  315. "content": [
  316. {"type": "text", "text": text}
  317. ]
  318. }
  319. if image_data:
  320. message_to_send["content"].append({
  321. "type": "image_url",
  322. "image_url": {
  323. "url": f"data:image/jpeg;base64,{image_data}",
  324. "detail": detail
  325. }
  326. })
  327. messages = [
  328. {"role": "system", "content": PERSONALITY_PROMPT}
  329. ] + conversation_history + [message_to_send]
  330. reply = await call_openai_model(
  331. model="gpt-4o-mini",
  332. messages=messages,
  333. max_tokens=450,
  334. temperature=1.0
  335. )
  336. return reply
  337. # ============================
  338. # Gestion du contenu de l'Historique
  339. # ============================
  340. async def remove_old_image_analyses(db_connection, new_analysis=False):
  341. """Supprime les anciennes analyses d'images de l'historique."""
  342. global conversation_history, last_analysis_index, messages_since_last_analysis
  343. if new_analysis:
  344. logger.debug("Nouvelle analyse détectée. Suppression des anciennes analyses.")
  345. conversation_history = [
  346. msg for msg in conversation_history
  347. if not (msg.get("role") == "system" and msg.get("content", "").startswith("__IMAGE_ANALYSIS__:"))
  348. ]
  349. last_analysis_index = len(conversation_history)
  350. messages_since_last_analysis = 0
  351. # Supprimer les analyses d'images de la base de données
  352. try:
  353. cursor = db_connection.cursor()
  354. cursor.execute("DELETE FROM conversation_history WHERE role = 'system' AND content LIKE '__IMAGE_ANALYSIS__:%'")
  355. db_connection.commit()
  356. logger.info("Toutes les anciennes analyses d'image ont été supprimées de la base de données.")
  357. except Error as e:
  358. logger.error(f"Erreur lors de la suppression des analyses d'image: {e}")
  359. finally:
  360. cursor.close()
  361. async def add_to_conversation_history(db_connection, new_message):
  362. global conversation_history, last_analysis_index, messages_since_last_analysis
  363. # Exclure le PERSONALITY_PROMPT de l'historique
  364. if new_message.get("role") == "system" and new_message.get("content") == PERSONALITY_PROMPT:
  365. logger.debug("PERSONALITY_PROMPT système non ajouté à l'historique.")
  366. return
  367. # Gérer les analyses d'images
  368. if new_message.get("role") == "system" and new_message.get("content", "").startswith("__IMAGE_ANALYSIS__:"):
  369. await remove_old_image_analyses(db_connection, new_analysis=True)
  370. # Ajouter le message à l'historique en mémoire
  371. conversation_history.append(new_message)
  372. # Sauvegarder dans la base de données
  373. save_message_to_db(db_connection, new_message.get("role"), new_message.get("content"))
  374. logger.debug(f"Message ajouté à l'historique. Taille actuelle : {len(conversation_history)}")
  375. # Mettre à jour les indices pour les analyses d'images
  376. if new_message.get("role") == "system" and new_message.get("content", "").startswith("__IMAGE_ANALYSIS__:"):
  377. last_analysis_index = len(conversation_history) - 1
  378. messages_since_last_analysis = 0
  379. else:
  380. await remove_old_image_analyses(db_connection, new_analysis=False)
  381. # Limiter l'historique à 150 messages
  382. if len(conversation_history) > 150:
  383. excess = len(conversation_history) - 150
  384. conversation_history = conversation_history[excess:]
  385. # Supprimer les messages les plus anciens de la base de données
  386. try:
  387. cursor = db_connection.cursor()
  388. cursor.execute("DELETE FROM conversation_history ORDER BY id ASC LIMIT %s", (excess,))
  389. db_connection.commit()
  390. logger.debug(f"{excess} messages les plus anciens ont été supprimés de la base de données pour maintenir l'historique à 150 messages.")
  391. except Error as e:
  392. logger.error(f"Erreur lors de la suppression des anciens messages: {e}")
  393. finally:
  394. cursor.close()
  395. # =====================
  396. # Gestion des Événements Discord
  397. # =====================
  398. @client_discord.event
  399. async def on_ready():
  400. """Événement déclenché lorsque le bot est prêt."""
  401. logger.info(f'{BOT_NAME} connecté en tant que {client_discord.user}')
  402. if not conversation_history:
  403. logger.info("Aucun historique trouvé. L'historique commence vide.")
  404. # Envoyer un message de version dans le canal Discord
  405. channel = client_discord.get_channel(chatgpt_channel_id)
  406. if channel:
  407. try:
  408. embed = discord.Embed(
  409. title="Bot Démarré",
  410. description=f"🎉 {BOT_NAME} est en ligne ! Version {BOT_VERSION}",
  411. color=0x00ff00 # Vert
  412. )
  413. await channel.send(embed=embed)
  414. logger.info(f"Message de connexion envoyé dans le canal ID {chatgpt_channel_id}")
  415. except discord.Forbidden:
  416. logger.error(f"Permissions insuffisantes pour envoyer des messages dans le canal ID {chatgpt_channel_id}.")
  417. except discord.HTTPException as e:
  418. logger.error(f"Erreur lors de l'envoi du message de connexion : {e}")
  419. else:
  420. logger.error(f"Canal avec ID {chatgpt_channel_id} non trouvé.")
  421. @client_discord.event
  422. async def on_message(message):
  423. """Événement déclenché lorsqu'un message est envoyé dans un canal suivi."""
  424. global conversation_history, last_analysis_index, messages_since_last_analysis
  425. # Ignorer les messages provenant d'autres canaux ou du bot lui-même
  426. if message.channel.id != chatgpt_channel_id or message.author == client_discord.user:
  427. return
  428. user_text = message.content.strip()
  429. # Commande de réinitialisation de l'historique
  430. if user_text.lower() == "!reset_history":
  431. if not message.author.guild_permissions.administrator:
  432. await message.channel.send("❌ Vous n'avez pas la permission d'utiliser cette commande.")
  433. return
  434. conversation_history = []
  435. try:
  436. cursor = db_connection.cursor()
  437. cursor.execute("DELETE FROM conversation_history")
  438. db_connection.commit()
  439. logger.info(f"Historique des conversations réinitialisé par {message.author}.")
  440. await message.channel.send("✅ L'historique des conversations a été réinitialisé.")
  441. except Error as e:
  442. logger.error(f"Erreur lors de la réinitialisation de l'historique: {e}")
  443. await message.channel.send("❌ Une erreur est survenue lors de la réinitialisation de l'historique.")
  444. finally:
  445. cursor.close()
  446. return
  447. # Traiter les pièces jointes
  448. image_data = None
  449. file_content = None
  450. attachment_filename = None
  451. allowed_extensions = ['.txt', '.py', '.html', '.css', '.js']
  452. if message.attachments:
  453. for attachment in message.attachments:
  454. if any(attachment.filename.lower().endswith(ext) for ext in allowed_extensions):
  455. file_content = await read_text_file(attachment)
  456. attachment_filename = attachment.filename
  457. break
  458. elif attachment.content_type and attachment.content_type.startswith('image/'):
  459. image_data = await encode_image_from_attachment(attachment, mode='high')
  460. break
  461. # Traitement des images
  462. if image_data:
  463. has_user_text = has_text(user_text)
  464. user_text_to_use = user_text if has_user_text else None
  465. temp_msg = await message.channel.send(f"*{BOT_NAME} observe l'image...*")
  466. try:
  467. # Analyser l'image avec GPT-4o
  468. analysis = await call_gpt4o_for_image_analysis(image_data, user_text=user_text_to_use)
  469. if analysis:
  470. # Ajouter l'analyse à l'historique
  471. analysis_message = {
  472. "role": "system",
  473. "content": f"__IMAGE_ANALYSIS__:{analysis}"
  474. }
  475. await add_to_conversation_history(db_connection, analysis_message)
  476. # Générer une réponse basée sur l'analyse
  477. reply = await call_gpt4o_mini_with_analysis(analysis, message.author.name, user_text, has_user_text)
  478. if reply:
  479. await temp_msg.delete()
  480. await message.channel.send(reply)
  481. # Construire et ajouter les messages à l'historique
  482. user_message_text = f"{user_text} (a posté une image.)" if has_user_text else (
  483. "Une image a été postée, mais elle n'est pas disponible pour analyse directe. Veuillez vous baser uniquement sur l'analyse fournie."
  484. )
  485. user_message = {
  486. "role": "user",
  487. "content": [
  488. {"type": "text", "text": f"{message.author.name} dit : {user_message_text}"}
  489. ]
  490. }
  491. assistant_message = {
  492. "role": "assistant",
  493. "content": reply
  494. }
  495. await add_to_conversation_history(db_connection, user_message)
  496. await add_to_conversation_history(db_connection, assistant_message)
  497. else:
  498. await temp_msg.delete()
  499. await message.channel.send("Désolé, je n'ai pas pu générer une réponse.")
  500. else:
  501. await temp_msg.delete()
  502. await message.channel.send("Désolé, je n'ai pas pu analyser l'image.")
  503. except Exception as e:
  504. await temp_msg.delete()
  505. await message.channel.send("Une erreur est survenue lors du traitement de l'image.")
  506. logger.error(f"Erreur lors du traitement de l'image: {e}")
  507. return # Ne pas continuer le traitement après une image
  508. # Ajouter le contenu du fichier au texte de l'utilisateur si un fichier est présent
  509. if file_content:
  510. user_text += f"\nContenu du fichier {attachment_filename}:\n{file_content}"
  511. # Vérifier si le texte n'est pas vide
  512. if not has_text(user_text):
  513. return # Ne pas appeler l'API si le texte est vide
  514. # Appeler l'API OpenAI pour le texte
  515. reply = await call_openai_api(user_text, message.author.name)
  516. if reply:
  517. await message.channel.send(reply)
  518. # Construire et ajouter les messages à l'historique
  519. user_message = {
  520. "role": "user",
  521. "content": [
  522. {"type": "text", "text": f"{message.author.name} dit : {user_text}"}
  523. ]
  524. }
  525. assistant_message = {
  526. "role": "assistant",
  527. "content": reply
  528. }
  529. await add_to_conversation_history(db_connection, user_message)
  530. await add_to_conversation_history(db_connection, assistant_message)
  531. # ============================
  532. # Démarrage du Bot Discord
  533. # ============================
  534. if __name__ == "__main__":
  535. db_connection = create_db_connection()
  536. if db_connection:
  537. load_conversation_history(db_connection)
  538. client_discord.run(DISCORD_TOKEN)
  539. db_connection.close()
  540. else:
  541. logger.error("Le bot ne peut pas démarrer sans connexion à la base de données.")