import os import sys import tkinter as tk from tkinter import filedialog, messagebox, ttk import threading import subprocess import time # Token HF en dur (depuis votre message) HF_TOKEN = "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" class MedicalScribeGUI: def __init__(self, root): self.root = root self.root.title("Medical AI Scribe - v1.0") self.root.geometry("850x750") self.root.configure(bg="#f8f9fa") # Variables self.audio_path = tk.StringVar() self.selected_model = tk.StringVar() self.status_var = tk.StringVar(value="Système prêt.") self.setup_ui() # Charger les modèles Ollama au démarrage de manière asynchrone threading.Thread(target=self.load_ollama_models, daemon=True).start() def setup_ui(self): main_frame = tk.Frame(self.root, bg="#f8f9fa", padx=20, pady=20) main_frame.pack(fill=tk.BOTH, expand=True) # 1. Sélection du fichier tk.Label(main_frame, text="Fichier Audio :", bg="#f8f9fa", font=("Arial", 10, "bold")).pack(anchor="w") file_frame = tk.Frame(main_frame, bg="#f8f9fa") file_frame.pack(fill="x", pady=(5, 15)) tk.Entry(file_frame, textvariable=self.audio_path, font=("Arial", 10), width=80).pack(side="left", padx=(0, 10)) tk.Button(file_frame, text="Parcourir", command=self.browse_file, bg="#dee2e6").pack(side="left") # 2. Sélection du modèle tk.Label(main_frame, text="Modèle Ollama pour la synthèse :", bg="#f8f9fa", font=("Arial", 10, "bold")).pack(anchor="w") # On permet d'écrire le nom du modèle s'il n'est pas dans la liste self.model_combo = ttk.Combobox(main_frame, textvariable=self.selected_model, width=50, font=("Arial", 10)) self.model_combo['values'] = ["Chargement des modèles..."] self.model_combo.set("gpt-oss:120b-cloud") self.model_combo.pack(anchor="w", pady=(5, 15)) # 3. Édition du Prompt tk.Label(main_frame, text="Prompt pour la synthèse médicale :", bg="#f8f9fa", font=("Arial", 10, "bold")).pack(anchor="w") self.prompt_text = tk.Text(main_frame, height=10, font=("Arial", 10), padx=5, pady=5) self.prompt_text.pack(fill="x", pady=(5, 15)) default_prompt = ( "Tu es un expert médical assistant. Tu dois analyser la transcription d'une réunion médicale.\n" "Ta mission est de produire une synthèse structurée incluant :\n" "1. Objet de la réunion / Motif de consultation.\n" "2. Éléments clés de la discussion (Symptômes, antécédents, examens évoqués).\n" "3. Décisions prises ou Diagnostic provisoire.\n" "4. Plan d'action (Traitements, examens complémentaires, prochain RDV).\n\n" "Règle : Terminologie médicale précise et style synthétique." ) self.prompt_text.insert("1.0", default_prompt) # 4. Bouton de lancement self.run_btn = tk.Button(main_frame, text="LANCER LE PIPELINE COMPLET", bg="#28a745", fg="white", font=("Arial", 12, "bold"), relief=tk.FLAT, pady=12, command=self.start_pipeline_thread) self.run_btn.pack(fill="x", pady=10) # 5. Zone de Logs tk.Label(main_frame, text="Logs de traitement :", bg="#f8f9fa", font=("Arial", 10, "bold")).pack(anchor="w") self.log_area = tk.Text(main_frame, height=12, bg="#1e1e1e", fg="#00ff00", font=("Consolas", 9), padx=10, pady=10) self.log_area.pack(fill=tk.BOTH, expand=True, pady=5) # Status bar tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor="w", bg="#e9ecef").pack(side=tk.BOTTOM, fill=tk.X) def browse_file(self): filename = filedialog.askopenfilename(filetypes=[("Audio files", "*.wav *.mp3 *.m4a *.flac *.ogg")]) if filename: self.audio_path.set(filename) def load_ollama_models(self): """Tente de lister les modèles Ollama de manière robuste.""" try: import ollama print("[DEBUG] Connexion à Ollama...") response = ollama.list() # On essaie d'extraire les noms de différentes manières selon la version de l'API models = [] if hasattr(response, 'models'): models = response.models elif isinstance(response, dict): models = response.get('models', []) model_names = [] for m in models: if hasattr(m, 'model'): # Nouveau format model_names.append(m.model) elif isinstance(m, dict): name = m.get('model') or m.get('name') if name: model_names.append(name) if model_names: self.model_combo['values'] = model_names if "gpt-oss:120b-cloud" in model_names: self.selected_model.set("gpt-oss:120b-cloud") elif "gpt-oss:latest" in model_names: self.selected_model.set("gpt-oss:latest") print(f"[DEBUG] {len(model_names)} modèles trouvés.") else: self.model_combo['values'] = ["gpt-oss:120b-cloud", "llama3.3:70b"] except Exception as e: print(f"[DEBUG] Erreur Ollama : {e}") self.model_combo['values'] = ["gpt-oss:120b-cloud", "llama3.3:70b"] def log(self, message): self.log_area.insert(tk.END, message + "\n") self.log_area.see(tk.END) self.root.update_idletasks() def start_pipeline_thread(self): if not self.audio_path.get(): messagebox.showwarning("Attention", "Veuillez d'abord sélectionner un fichier audio.") return self.run_btn.config(state="disabled", bg="#6c757d") self.log_area.delete("1.0", tk.END) self.status_var.set("Traitement en cours... (Transcription + Diarisation)") threading.Thread(target=self.run_pipeline, daemon=True).start() def run_pipeline(self): audio_file = self.audio_path.get() model_name = self.selected_model.get() custom_prompt = self.prompt_text.get("1.0", tk.END).strip() try: # Étape 1 : Diarisation et Transcription self.log("--- ÉTAPE 1 : DIARISATION & TRANSCRIPTION WHISPER LARGE-V3 ---") self.log(f"Fichier cible : {os.path.basename(audio_file)}") script_dir = os.path.dirname(os.path.abspath(__file__)) env = os.environ.copy() env["HF_TOKEN"] = HF_TOKEN p = subprocess.Popen( [sys.executable, os.path.join(script_dir, "medical_diarizer.py"), audio_file], env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1 ) for line in p.stdout: self.log(line.strip()) p.wait() transcript_file = audio_file.rsplit('.', 1)[0] + "_diarized.txt" if not os.path.exists(transcript_file): raise Exception("Échec de la transcription : le fichier texte n'a pas été généré.") # Étape 2 : Synthèse IA self.log(f"\n--- ÉTAPE 2 : GÉNÉRATION SYNTHÈSE AVEC {model_name} ---") self.status_var.set(f"Analyse par l'IA ({model_name})...") with open(transcript_file, "r", encoding="utf-8") as f: content = f.read() import ollama response = ollama.chat( model=model_name, messages=[ {"role": "system", "content": custom_prompt}, {"role": "user", "content": f"Voici le transcript à analyser :\n\n{content}"} ] ) summary = response['message']['content'] summary_file = audio_file.rsplit('.', 1)[0] + "_summary.md" with open(summary_file, "w", encoding="utf-8") as f: f.write(summary) self.log("\n" + "="*50) self.log("PIPELINE MÉDICAL TERMINÉ !") self.log(f"Transcript structuré : {os.path.basename(transcript_file)}") self.log(f"Synthèse sauvegardée : {os.path.basename(summary_file)}") self.log("="*50) self.status_var.set("Terminé avec succès.") messagebox.showinfo("Succès", "Traitement terminé avec succès !") except Exception as e: self.log(f"\n[ERREUR] {str(e)}") self.status_var.set("Erreur durant le traitement.") messagebox.showerror("Erreur de Pipeline", str(e)) finally: self.run_btn.config(state="normal", bg="#28a745") if __name__ == "__main__": root = tk.Tk() app = MedicalScribeGUI(root) root.mainloop()