#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script de surveillance de répertoire pour l'injection automatique dans PrivateGPT Ce script surveille un répertoire et injecte automatiquement les nouveaux fichiers dans PrivateGPT dès qu'ils sont ajoutés ou modifiés. """ import os import sys import time import logging import argparse import subprocess from pathlib import Path from typing import Set, Dict, List from datetime import datetime try: from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler, FileCreatedEvent, FileModifiedEvent except ImportError: print("Bibliothèque 'watchdog' non installée. Installation en cours...") subprocess.run([sys.executable, "-m", "pip", "install", "watchdog"]) from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler, FileCreatedEvent, FileModifiedEvent # Configuration du logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(), logging.FileHandler("pgpt_watch_directory.log") ] ) logger = logging.getLogger(__name__) # Extensions de fichiers couramment supportées par PrivateGPT SUPPORTED_EXTENSIONS = { '.pdf', '.txt', '.md', '.doc', '.docx', '.ppt', '.pptx', '.xls', '.xlsx', '.csv', '.epub', '.html', '.htm' } class DocumentHandler(FileSystemEventHandler): """Gestionnaire d'événements pour les fichiers de documents.""" def __init__(self, watch_dir: str, ingest_script: str, pgpt_url: str, extensions: Set[str], delay: int = 5): """ Initialise le gestionnaire d'événements. Args: watch_dir: Répertoire à surveiller ingest_script: Chemin vers le script d'injection pgpt_url: URL de l'API PrivateGPT extensions: Extensions de fichiers à traiter delay: Délai en secondes à attendre avant le traitement (évite de traiter des fichiers partiellement écrits) """ self.watch_dir = os.path.abspath(watch_dir) self.ingest_script = os.path.abspath(ingest_script) self.pgpt_url = pgpt_url self.extensions = extensions self.delay = delay # Queue pour les fichiers en attente de traitement self.pending_files: Dict[str, float] = {} # Vérifier que le script d'injection existe if not os.path.exists(self.ingest_script): logger.error(f"Le script d'injection {self.ingest_script} n'existe pas!") raise FileNotFoundError(f"Script d'injection introuvable: {self.ingest_script}") def on_created(self, event): """Appelé lorsqu'un fichier est créé.""" if not event.is_directory: self._handle_file_event(event) def on_modified(self, event): """Appelé lorsqu'un fichier est modifié.""" if not event.is_directory: self._handle_file_event(event) def _handle_file_event(self, event): """Traite un événement de fichier (création ou modification).""" file_path = event.src_path file_ext = os.path.splitext(file_path)[1].lower() # Ignorer les fichiers non supportés if file_ext not in self.extensions: return # Ignorer les fichiers temporaires et cachés file_name = os.path.basename(file_path) if file_name.startswith('.') or file_name.startswith('~') or file_name.endswith('.tmp'): return # Ajouter à la queue avec l'horodatage actuel self.pending_files[file_path] = time.time() logger.info(f"Fichier détecté: {file_path} (en attente pendant {self.delay} secondes)") def process_pending_files(self): """Traite les fichiers en attente qui ont dépassé le délai d'attente.""" current_time = time.time() files_to_process: List[str] = [] # Identifier les fichiers prêts à être traités for file_path, timestamp in list(self.pending_files.items()): if current_time - timestamp >= self.delay: if os.path.exists(file_path): # Vérifier que le fichier existe toujours files_to_process.append(file_path) self.pending_files.pop(file_path) # Traiter les fichiers par lot if files_to_process: self._ingest_files(files_to_process) def _ingest_files(self, files: List[str]): """ Injecte une liste de fichiers en utilisant le script d'injection. Args: files: Liste des chemins de fichiers à injecter """ try: # Créer un répertoire temporaire pour stocker la liste des fichiers temp_dir = os.path.join(os.path.dirname(self.ingest_script), "temp") os.makedirs(temp_dir, exist_ok=True) # Créer un fichier de liste timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") list_file = os.path.join(temp_dir, f"files_to_ingest_{timestamp}.txt") with open(list_file, "w") as f: for file_path in files: f.write(f"{file_path}\n") # Construire la commande pour le script d'injection for file_path in files: file_dir = os.path.dirname(file_path) logger.info(f"Injection de {file_path}...") cmd = [ sys.executable, self.ingest_script, "-d", file_dir, "-u", self.pgpt_url, "--extensions", os.path.splitext(file_path)[1][1:] # Extension sans le point ] # Exécuter la commande process = subprocess.run( cmd, capture_output=True, text=True ) if process.returncode == 0: logger.info(f"Injection réussie de {file_path}") else: logger.error(f"Échec de l'injection de {file_path}: {process.stderr}") except Exception as e: logger.error(f"Erreur lors de l'injection des fichiers: {str(e)}") def parse_arguments(): """Parse les arguments de ligne de commande.""" parser = argparse.ArgumentParser( description="Surveille un répertoire et injecte automatiquement les nouveaux fichiers dans PrivateGPT" ) parser.add_argument( "-d", "--directory", type=str, required=True, help="Chemin du répertoire à surveiller" ) parser.add_argument( "-s", "--script", type=str, default=None, help="Chemin vers le script auto_ingest.py (par défaut: détection automatique)" ) parser.add_argument( "-u", "--url", type=str, default="http://localhost:8001", help="URL de l'API PrivateGPT (défaut: http://localhost:8001)" ) parser.add_argument( "--delay", type=int, default=5, help="Délai en secondes avant de traiter un nouveau fichier (défaut: 5)" ) parser.add_argument( "--extensions", nargs="+", help="Liste d'extensions spécifiques à surveiller (ex: pdf txt)" ) return parser.parse_args() def main(): """Fonction principale.""" args = parse_arguments() # Préparation des extensions si spécifiées extensions = set(args.extensions) if args.extensions else SUPPORTED_EXTENSIONS # Assurer que les extensions commencent par un point extensions = {ext if ext.startswith('.') else f'.{ext}' for ext in extensions} # Déterminer le chemin du script d'injection if args.script: ingest_script = args.script else: # Utiliser le script auto_ingest.py dans le même répertoire que ce script ingest_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), "auto_ingest.py") # Créer le répertoire de surveillance s'il n'existe pas watch_dir = os.path.abspath(args.directory) if not os.path.exists(watch_dir): logger.info(f"Création du répertoire de surveillance: {watch_dir}") os.makedirs(watch_dir, exist_ok=True) logger.info(f"Démarrage de la surveillance de {watch_dir}") logger.info(f"URL PrivateGPT: {args.url}") logger.info(f"Extensions surveillées: {', '.join(extensions)}") logger.info(f"Délai de traitement: {args.delay} secondes") # Initialiser le gestionnaire et l'observateur event_handler = DocumentHandler( watch_dir=watch_dir, ingest_script=ingest_script, pgpt_url=args.url, extensions=extensions, delay=args.delay ) observer = Observer() observer.schedule(event_handler, path=watch_dir, recursive=True) observer.start() try: logger.info("Surveillance en cours... (Ctrl+C pour quitter)") while True: # Traiter les fichiers en attente event_handler.process_pending_files() time.sleep(1) except KeyboardInterrupt: logger.info("\nInterruption par l'utilisateur. Arrêt de la surveillance.") observer.stop() observer.join() if __name__ == "__main__": try: main() except Exception as e: logger.error(f"Erreur non gérée: {str(e)}", exc_info=True) sys.exit(1)