File size: 13,872 Bytes
446664e
 
 
 
 
6a0ca7e
b3f9d19
3733555
 
 
 
 
 
446664e
 
 
3733555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446664e
 
3733555
 
 
 
446664e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3733555
446664e
 
 
 
 
 
5ec64d2
446664e
 
3733555
5ec64d2
446664e
 
3733555
446664e
5ec64d2
446664e
 
3733555
446664e
5ec64d2
446664e
6407653
3733555
446664e
6407653
446664e
 
5ec64d2
6407653
 
3733555
6407653
 
3733555
446664e
6407653
446664e
6407653
3733555
6407653
 
3733555
6407653
446664e
 
3733555
446664e
 
 
 
5ec64d2
446664e
3733555
446664e
 
 
 
3dc384c
446664e
 
 
 
 
bea9f70
 
446664e
b24ee2b
446664e
 
b3f9d19
5ec64d2
b24ee2b
b3f9d19
8170733
3733555
b3f9d19
 
 
b24ee2b
 
 
 
 
 
 
 
 
 
 
 
 
2451ccd
 
 
b3f9d19
 
 
2451ccd
b3f9d19
bea9f70
b24ee2b
 
 
 
 
 
 
 
 
bea9f70
b24ee2b
 
b3f9d19
b24ee2b
 
b3f9d19
 
 
b24ee2b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b3f9d19
 
 
3733555
b3f9d19
 
 
 
b24ee2b
 
 
 
 
 
 
 
b3f9d19
 
 
 
3733555
b3f9d19
5ec64d2
446664e
3733555
446664e
 
 
 
 
3733555
446664e
 
 
 
 
3733555
446664e
 
3733555
446664e
 
 
 
 
 
 
 
 
3733555
446664e
 
 
5ec64d2
3733555
 
 
 
446664e
 
5ec64d2
446664e
 
 
 
3733555
446664e
 
 
 
 
3733555
446664e
6407653
446664e
3733555
446664e
 
 
3733555
446664e
 
 
 
6407653
446664e
3733555
446664e
 
 
 
6407653
446664e
3733555
8bc9850
446664e
3733555
446664e
47ad7bc
446664e
8bc9850
 
3733555
 
6407653
3733555
 
446664e
 
 
3733555
446664e
 
 
 
 
 
3733555
446664e
3733555
6407653
3733555
 
 
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
from flask import Flask, render_template, request, jsonify
import google.generativeai as genai
import os
from PIL import Image
import tempfile
import PIL.Image
import subprocess
import logging
from urllib.parse import urlparse
import requests
import time
import ssl
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# Configuration des logs
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
log_file = 'app.log'

# Handler pour fichier avec rotation
file_handler = RotatingFileHandler(log_file, maxBytes=10485760, backupCount=5)  # 10MB par fichier, max 5 fichiers
file_handler.setFormatter(log_formatter)

# Handler pour console
console_handler = logging.StreamHandler()
console_handler.setFormatter(log_formatter)

# Configuration du logger principal
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# Configuration de l'API Gemini
token = os.environ.get("TOKEN")
if not token:
    logger.error("Token API non trouvé dans les variables d'environnement")
    raise ValueError("Token API manquant")

genai.configure(api_key=token)

generation_config = {
    "temperature": 1,
    "max_output_tokens": 8192,
}

safety_settings = [
    {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
    {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
    {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
    {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
]

@app.route('/')
def generale():
    logger.info("Accès à la page principale")
    return render_template("generale.html")

def upload_and_process_file(file_path):
    """Upload et traite un fichier avec l'API Gemini avec gestion des erreurs améliorée"""
    max_retries = 3
    retry_delay = 2  # secondes
    
    for attempt in range(max_retries):
        try:
            logger.info(f"Tentative d'upload {attempt + 1}/{max_retries} pour {file_path}")
            
            # Vérification du fichier
            if not os.path.exists(file_path):
                logger.error(f"Fichier non trouvé: {file_path}")
                raise FileNotFoundError(f"Le fichier {file_path} n'existe pas")
                
            file_size = os.path.getsize(file_path)
            if file_size == 0:
                logger.error(f"Fichier vide: {file_path}")
                raise ValueError(f"Le fichier {file_path} est vide")
                
            # Upload du fichier
            uploaded_file = genai.upload_file(path=file_path)
            logger.info(f"Upload réussi: {uploaded_file.uri}")

            # Attente du traitement
            timeout = 300  # 5 minutes
            start_time = time.time()
            
            while uploaded_file.state.name == "PROCESSING":
                if time.time() - start_time > timeout:
                    logger.error("Timeout pendant le traitement du fichier")
                    raise TimeoutError("Timeout pendant le traitement du fichier")
                
                logger.debug(f"En attente du traitement... Temps écoulé: {int(time.time() - start_time)}s")
                time.sleep(10)
                uploaded_file = genai.get_file(uploaded_file.name)

            if uploaded_file.state.name == "FAILED":
                logger.error(f"Échec du traitement: {uploaded_file.state.name}")
                raise ValueError(f"Échec du traitement: {uploaded_file.state.name}")
                
            logger.info(f"Traitement terminé avec succès: {uploaded_file.uri}")
            return uploaded_file

        except ssl.SSLError as e:
            logger.error(f"Erreur SSL lors de l'upload (tentative {attempt + 1}): {e}")
            if attempt < max_retries - 1:
                time.sleep(retry_delay * (attempt + 1))
            else:
                raise
                
        except Exception as e:
            logger.error(f"Erreur lors de l'upload (tentative {attempt + 1}): {e}")
            if attempt < max_retries - 1:
                time.sleep(retry_delay * (attempt + 1))
            else:
                raise

def is_youtube_url(url):
    """Vérifie si l'URL est une URL YouTube"""
    parsed = urlparse(url)
    return any(domain in parsed.netloc for domain in ['youtube.com', 'youtu.be'])



def download_youtube_video(url):
    """Télécharge une vidéo YouTube en utilisant yt-dlp avec une configuration adaptée aux conteneurs"""
    try:
        with tempfile.TemporaryDirectory() as temp_dir:
            output_template = os.path.join(temp_dir, '%(title)s.%(ext)s')
            
            # Configuration optimisée pour environnement conteneurisé
            command = [
                'yt-dlp',
                '--format', 'best[filesize<50M]',
                '--quiet',
                '--no-warnings',
                '--output', output_template,
                '--extract-audio',  # Tenter d'abord l'extraction audio si la vidéo échoue
                '--format-sort', 'res,ext:mp4:m4a',  # Prioriser les formats plus légers
                '--user-agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
                '--referer', 'https://www.youtube.com/',
                '--geo-bypass',  # Contourner les restrictions géographiques
                '--no-check-certificates',  # Ignorer les erreurs de certificat
                '--force-ipv4',  # Forcer IPv4 pour plus de stabilité
                '--ignore-errors',  # Continuer en cas d'erreurs mineures
                '--no-playlist',  # Éviter le téléchargement de playlists
                '--extractor-retries', '3',
                '--file-access-retries', '3',
                '--fragment-retries', '3',
                '--retry-sleep', '5',
                '--sleep-requests','1.5',
                '--min-sleep-interval','60',
                '--max-sleep-interval','90',
                url
            ]
            
            logger.info(f"Début du, téléchargement de la vidéo: {url}")
            
            try:
                # Vérifier la version de yt-dlp
                version_process = subprocess.run(['yt-dlp', '--version'], 
                                              capture_output=True, 
                                              text=True, 
                                              check=True)
                logger.info(f"Version de yt-dlp: {version_process.stdout.strip()}")
            except subprocess.SubprocessError as e:
                logger.error(f"Erreur lors de la vérification de yt-dlp: {e}")
                raise RuntimeError("yt-dlp n'est pas correctement installé")
            
            # Premier essai avec la configuration standard
            process = subprocess.run(
                command,
                capture_output=True,
                text=True
            )
            
            if process.returncode != 0:
                logger.warning(f"Premier essai échoué: {process.stderr}")
                logger.info("Tentative avec configuration alternative...")
                
                # Configuration alternative
                alternative_command = command.copy()
                alternative_command.extend([
                    '--format', 'worst',  # Utiliser la qualité la plus basse
                    '--prefer-insecure',  # Préférer les connexions non-sécurisées si nécessaire
                    '--socket-timeout', '30',
                    '--external-downloader', 'aria2c',  # Utiliser aria2c comme downloader alternatif
                    '--external-downloader-args', 'aria2c:"--min-split-size=1M --max-connection-per-server=16 --max-concurrent-downloads=16 --split=16"'
                ])
                
                process = subprocess.run(
                    alternative_command,
                    capture_output=True,
                    text=True
                )
                
                if process.returncode != 0:
                    logger.error(f"Échec du téléchargement après tentatives alternatives: {process.stderr}")
                    raise Exception(f"Échec du téléchargement: {process.stderr}")
            
            downloaded_files = os.listdir(temp_dir)
            if not downloaded_files:
                logger.error("Aucun fichier n'a été téléchargé")
                raise FileNotFoundError("Aucun fichier n'a été téléchargé")
            
            video_path = os.path.join(temp_dir, downloaded_files[0])
            
            # Vérifier la taille du fichier
            file_size = os.path.getsize(video_path)
            if file_size == 0:
                raise ValueError("Le fichier téléchargé est vide")
            
            logger.info(f"Fichier téléchargé: {video_path} ({file_size} bytes)")
            
            # Copier le fichier vers un emplacement temporaire
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(video_path)[1])
            with open(video_path, 'rb') as f:
                temp_file.write(f.read())
            
            logger.info(f"Vidéo téléchargée avec succès: {temp_file.name}")
            return temp_file.name
                
    except Exception as e:
        logger.error(f"Erreur lors du téléchargement de la vidéo: {e}")
        return None

def telecharger_pdf(url):
    """Télécharge un PDF et retourne le chemin du fichier"""
    try:
        logger.info(f"Début du téléchargement du PDF: {url}")
        response = requests.get(url)
        response.raise_for_status()

        with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
            temp_file.write(response.content)
            logger.info(f"PDF téléchargé avec succès: {temp_file.name}")
            return temp_file.name
    except Exception as e:
        logger.error(f"Erreur lors du téléchargement du PDF: {e}")
        return None

def allowed_file(filename):
    """Vérifie si l'extension du fichier est autorisée"""
    ALLOWED_EXTENSIONS = {'pdf', 'png', 'jpg', 'jpeg', 'gif', 'mp4', 'mov', 'avi'}
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/submit', methods=['POST'])
def submit_question():
    logger.info("Nouvelle soumission reçue")
    question = request.form.get('question')
    urls = request.form.getlist('urls')
    files = request.files.getlist('files')
    
    logger.info(f"Question reçue: {question}")
    logger.info(f"URLs reçues: {urls}")
    logger.info(f"Nombre de fichiers reçus: {len(files)}")
    
    content = [question]
    temp_files = []
    
    try:
        # Traitement des fichiers uploadés
        for file in files:
            if file and allowed_file(file.filename):
                logger.info(f"Traitement du fichier: {file.filename}")
                with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as temp_file:
                    file.save(temp_file.name)
                    temp_files.append(temp_file.name)
                    if file.filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')):
                        content.append(PIL.Image.open(temp_file.name))
                        logger.info(f"Image ajoutée au contenu: {file.filename}")
                    else:
                        uploaded_file = upload_and_process_file(temp_file.name)
                        content.append(uploaded_file)
                        logger.info(f"Fichier uploadé et ajouté au contenu: {file.filename}")

        # Traitement des URLs
        for url in urls:
            logger.info(f"Traitement de l'URL: {url}")
            if is_youtube_url(url):
                video_path = download_youtube_video(url)
                if video_path:
                    temp_files.append(video_path)
                    uploaded_file = upload_and_process_file(video_path)
                    content.append(uploaded_file)
                    logger.info(f"Vidéo YouTube traitée et ajoutée au contenu: {url}")
            elif url.lower().endswith('.pdf'):
                pdf_path = telecharger_pdf(url)
                if pdf_path:
                    temp_files.append(pdf_path)
                    uploaded_file = upload_and_process_file(pdf_path)
                    content.append(uploaded_file)
                    logger.info(f"PDF téléchargé et ajouté au contenu: {url}")

        # Génération de contenu avec Gemini
        logger.info("Initialisation du modèle Gemini")
        model = genai.GenerativeModel(
            model_name="models/gemini-2.0-flash",
            safety_settings=safety_settings,
            system_instruction="Tu es un assistant intelligent. ton but est d'assister au mieux que tu peux. tu as été créé par Aenir et tu t'appelles Mariam."
        )
        
        logger.info("Génération de la réponse")
        response = model.generate_content(content, request_options={"timeout": 600})
        logger.info("Réponse générée avec succès")
        
        return jsonify({"response": response.text})

    except Exception as e:
        logger.error(f"Erreur lors du traitement de la requête: {e}", exc_info=True)
        return jsonify({"error": str(e)}), 500
    finally:
        # Nettoyage des fichiers temporaires
        for temp_file in temp_files:
            try:
                os.unlink(temp_file)
                logger.debug(f"Fichier temporaire supprimé: {temp_file}")
            except Exception as e:
                logger.error(f"Erreur lors de la suppression du fichier temporaire {temp_file}: {e}")

if __name__ == '__main__':
    logger.info("Démarrage de l'application")
    app.run()