File size: 8,725 Bytes
5c5171d
886cc1a
 
 
5c5171d
00a0107
5c5171d
00a0107
5c5171d
 
886cc1a
 
 
 
 
 
4d7b269
7ee1cf5
 
 
 
 
4d7b269
 
7ee1cf5
 
 
4d7b269
7ee1cf5
 
 
4d7b269
 
 
 
 
7ee1cf5
 
4d7b269
7ee1cf5
 
 
4d7b269
7ee1cf5
 
 
4d7b269
7ee1cf5
 
 
4d7b269
7ee1cf5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c5171d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4dd430a
 
5c5171d
 
4dd430a
886cc1a
5c5171d
886cc1a
 
 
 
 
 
 
5c5171d
 
 
886cc1a
 
5c5171d
886cc1a
 
 
 
 
3065104
5c5171d
4dd430a
5c5171d
4dd430a
5c5171d
4dd430a
 
 
5c5171d
886cc1a
 
 
 
5c5171d
886cc1a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215b7b9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# model_handler.py (Continuation du fichier)
import sys
from google import genai
from google.genai.errors import APIError
from google.genai.types import Part # Importation de Part est déjà là, c'est bien.
import os 
from typing import Generator, List, Dict, Union # Ajout d'Union
from config import GEMINI_MODEL_NAME, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE, SYSTEM_PROMPT
import base64 # NOUVEAU: Pour décoder les fichiers base64


# --- Configuration du Client Gemini ---

client = None
MODEL_LOADED_SUCCESSFULLY = False


def run_api_self_test(client_instance):
    """Effectue un test simple pour vérifier si l'API fonctionne."""
    try:
        print("INFO: Exécution du test de connexion à l'API Gemini...", file=sys.stderr)
        
        # Test minimal: demandons simplement une salutation
        test_content = ["Salutation brève"] 
        
        response = client_instance.models.generate_content(
            model=GEMINI_MODEL_NAME, 
            contents=test_content,
            config=genai.types.GenerateContentConfig(max_output_tokens=10) # Limiter la réponse
        )
        
        # CHANGEMENT : Vérification simplifiée pour s'assurer qu'un objet 'response' a été reçu.
        if response is not None:
            response_text_info = response.text[:30] if response.text else "[Réponse sans texte]"
            print("INFO: ✅ Test de connexion API réussi. Le modèle est joignable.", file=sys.stderr)
            print(f"INFO: Réponse du test (début): {response_text_info}...", file=sys.stderr)
            return True
        else:
            print("ALERTE: ❌ Test de connexion API échoué. Objet de réponse None.", file=sys.stderr)
            return False
            
    except APIError as e:
        # Ceci gère les problèmes de clé invalide, quota, etc.
        print(f"ALERTE: ❌ Test de connexion API échoué (APIError). Vérifiez la validité de la clé: {e.message}", file=sys.stderr)
        return False
    except Exception as e:
        # Erreur générale (connexion, SDK, etc.)
        print(f"ALERTE: ❌ Test de connexion API échoué (Erreur Générale): {e}", file=sys.stderr)
        return False


def get_model_instance():
    """Initialise ou retourne l'instance du client Gemini."""
    global client, MODEL_LOADED_SUCCESSFULLY
    
    if client is None:
        
        print("INFO: Tentative d'initialisation du client Gemini...", file=sys.stderr)
        api_key = os.environ.get("GEMINI_API_KEY")
        
        if not api_key:
            print("ALERTE: ❌ Variable d'environnement 'GEMINI_API_KEY' non trouvée/vide.", file=sys.stderr)
            MODEL_LOADED_SUCCESSFULLY = False
            return 
            
        print("INFO: Clé API 'GEMINI_API_KEY' trouvée dans les variables d'environnement.", file=sys.stderr)
        
        try:
            # 1. Initialisation du client
            client = genai.Client(api_key=api_key) 
            print("INFO: Client Gemini initialisé. Tentative de self-test...", file=sys.stderr)
            
            # 2. Exécution du Self-Test
            if run_api_self_test(client):
                 MODEL_LOADED_SUCCESSFULLY = True
                 print(f"INFO: Configuration API réussie. Modèle LLM prêt. Utilisant {GEMINI_MODEL_NAME}.", file=sys.stderr)
            else:
                 # Le self-test a échoué (clé invalide ou autre problème API)
                 client = None # Forcer la réinitialisation si nécessaire
                 MODEL_LOADED_SUCCESSFULLY = False
                 print("ALERTE: ❌ Client Gemini non initialisé. Le self-test a échoué.", file=sys.stderr)
                 
        except Exception as e:
            # Ceci attrape les erreurs qui ne sont pas des APIError (ex: problème de librairie, etc.)
            print(f"ALERTE: ❌ Échec critique de l'initialisation du client Gemini: {e}", file=sys.stderr)
            client = None
            MODEL_LOADED_SUCCESSFULLY = False


# NOUVEAU: Fonction utilitaire pour créer un objet Part pour un fichier
def create_file_part(file_data: Dict) -> Part:
    """Crée un objet Part à partir d'un dictionnaire de données de fichier."""
    # Le backend Render doit fournir 'content' (base64) et 'mime_type' (ex: 'text/plain', 'image/png')
    
    # 1. Décoder le contenu de base64
    file_bytes = base64.b64decode(file_data.get('content'))
    mime_type = file_data.get('mime_type')
    
    # 2. Créer l'objet Part
    return Part.from_bytes(
        data=file_bytes,
        mime_type=mime_type
    )


# MODIFIÉ: Ajout du paramètre `files` à la fonction de normalisation
def normalize_history_for_gemini(
    history: List[Dict], 
    new_prompt: str, 
    files: List[Dict] = [] # NOUVEAU: Liste des fichiers à inclure
) -> List[Dict]:
    """
    Convertit l'historique de conversation et les nouveaux fichiers/prompt en 
    le format Contents attendu par l'API Gemini.
    """
    gemini_contents = []
    
    # 1. Traitement de l'historique (messages passés) - Reste le même
    for conversation_turn in history:
        for message in conversation_turn.get('messages', []):
            role = message.get('role')
            text = message.get('text')
            
            gemini_role = 'model' if role == 'assistant' else 'user'
            
            if text and gemini_role in ['user', 'model']:
                gemini_contents.append({
                    "role": gemini_role,
                    "parts": [{"text": text}]
                })

    # 2. Ajout du nouveau prompt et des fichiers de l'utilisateur (le message actuel)
    user_parts = []
    
    # D'abord les fichiers (s'il y en a)
    for file_data in files:
        # Tente de créer un Part pour chaque fichier
        try:
            part = create_file_part(file_data)
            user_parts.append(part)
        except Exception as e:
            # Gérer les erreurs de décodage ou de format
            print(f"Erreur de traitement de fichier: {e}", file=sys.stderr)
            # Optionnel: Ajouter un message d'erreur au prompt pour informer l'IA
            user_parts.append({"text": f"(Erreur: Le fichier {file_data.get('filename', 'inconnu')} n'a pas pu être lu)"})

    # Ensuite le texte du prompt
    if new_prompt:
        user_parts.append({"text": new_prompt})
        
    # Ajouter le tour de conversation utilisateur
    if user_parts:
        gemini_contents.append({
            "role": "user",
            "parts": user_parts
        })
    
    return gemini_contents


# MODIFIÉ: Ajout du paramètre `files` à la fonction de génération
def generate_text_response(
    prompt: str,
    history: List[Dict] = [],
    files: List[Dict] = [] # NOUVEAU: Liste des fichiers (base64)
) -> Generator[str, None, None]:
    """
    Génère une réponse en streaming de l'API Gemini en utilisant l'historique et les fichiers.
    """
    
    get_model_instance()
    if not MODEL_LOADED_SUCCESSFULLY or client is None:
        yield "Erreur: Le service Gemini n'est pas initialisé. Vérifiez la clé API."
        return

    # Si le prompt et les fichiers sont vides, on s'arrête
    if not prompt and not files:
        yield "Erreur: Le contenu de la requête (prompt et/ou fichiers) est vide."
        return

    # Configuration de la génération - Reste la même
    config = genai.types.GenerateContentConfig(
        system_instruction=SYSTEM_PROMPT,
        max_output_tokens=DEFAULT_MAX_TOKENS,
        temperature=DEFAULT_TEMPERATURE,
    )
    
    # MODIFIÉ: Préparation du contenu, y compris l'historique ET les fichiers
    try:
        contents = normalize_history_for_gemini(history, prompt, files) # PASSAGE DES FICHIERS
    except Exception as e:
        error_message = f"Erreur lors de la normalisation de l'historique et des fichiers pour Gemini: {e}"
        print(f"Erreur de normalisation: {error_message}", file=sys.stderr)
        yield f"Erreur: {error_message}"
        return
    
    try:
        # --- Appel à l'API STREAMING ---
        response_stream = client.models.generate_content_stream(
            model=GEMINI_MODEL_NAME,
            contents=contents, 
            config=config,
        )

        for chunk in response_stream:
            if chunk.text:
                yield chunk.text

    except APIError as e:
        error_message = f"Erreur de l'API Gemini: {e.message}"
        print(f"Erreur API: {error_message}", file=sys.stderr)
        yield f"Erreur: {error_message}"
        
    except Exception as e:
        error_message = f"Erreur inattendue lors de la génération: {str(e)}"
        print(f"Erreur Générale: {error_message}", file=sys.stderr)
        yield f"Erreur: {error_message}"