chat_ia.py — v2 (chemins corrigés pour Linux)

Bienvenue sur CFTPfr.fr Forums scripts chat_ia.py — v2 (chemins corrigés pour Linux)

  • Ce sujet est vide.
Affichage de 1 message (sur 1 au total)
  • Auteur
    Messages
  • #222
    Mario Da Conceicao
    Maître des clés

    Version corrigée du 25/07/2026 — étape 1 du portage Linux. Seul changement : le chemin de base n’est plus écrit en dur (D:\MIRA_Project\mira_ai), il se calcule automatiquement à partir de l’emplacement du fichier lui-même. Ça fonctionne pareil sous Windows et sous Linux, peu importe où le dossier est installé. Le reste du comportement est identique à la version d’origine (voir le sujet "chat_ia.py" précédent pour comparer).

    import tkinter as tk
    from tkinter import scrolledtext, simpledialog, messagebox
    from pathlib import Path
    import json
    import os
    import re
    import webbrowser
    import subprocess
    import ast
    import operator as op
    import importlib.util
    import sqlite3
    import hashlib
    import secrets
    import random
    from datetime import datetime
    from difflib import get_close_matches
    
    # --- CORRECTION : chemin calculé automatiquement, plus de "D:\..." écrit en dur ---
    # Fonctionne sous Windows ET sous Linux, peu importe où le dossier est installé.
    BASE_DIR = Path(__file__).resolve().parent
    PROJECT_ROOT = BASE_DIR.parent
    PLUGINS_DIR = BASE_DIR / "plugins"
    CONFIG_FILE = BASE_DIR / "config.json"
    KNOWLEDGE_FILE = BASE_DIR / "connaissances.json"
    HISTORY_FILE = BASE_DIR / "history" / "questions_non_comprises.txt"
    DB_FILE = BASE_DIR / "memory.db"
    
    SAFE_OPERATORS = {
        ast.Add: op.add,
        ast.Sub: op.sub,
        ast.Mult: op.mul,
        ast.Div: op.truediv,
        ast.FloorDiv: op.floordiv,
        ast.Mod: op.mod,
        ast.Pow: op.pow,
        ast.UAdd: op.pos,
        ast.USub: op.neg,
    }
    
    def hacher_mot_de_passe(mot_de_passe: str) -> str:
        salt = secrets.token_hex(16)
        h = hashlib.sha256((mot_de_passe + salt).encode("utf-8")).hexdigest()
        return f"{salt}:{h}"
    
    def verifier_mot_de_passe(mot_de_passe: str, stocke: str) -> bool:
        try:
            salt, h = stocke.split(":")
            return hashlib.sha256((mot_de_passe + salt).encode("utf-8")).hexdigest() == h
        except Exception:
            return False
    
    class AutoImprover:
        def __init__(self, db_file=DB_FILE):
            self.db_file = Path(db_file)
            self.db_file.parent.mkdir(parents=True, exist_ok=True)
            self.conn = sqlite3.connect(self.db_file)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS messages(
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ts TEXT,
                    sender TEXT,
                    content TEXT
                )
            """)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS needs(
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ts TEXT,
                    message TEXT,
                    domain TEXT,
                    priority INTEGER,
                    suggestion TEXT,
                    done INTEGER DEFAULT 0
                )
            """)
            self.conn.execute("""
                CREATE TABLE IF NOT EXISTS improvements(
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ts TEXT,
                    title TEXT,
                    description TEXT,
                    score INTEGER DEFAULT 0,
                    applied INTEGER DEFAULT 0
                )
            """)
            self.conn.commit()
    
        def log_message(self, sender, content):
            self.conn.execute(
                "INSERT INTO messages(ts, sender, content) VALUES(?,?,?)",
                (datetime.now().isoformat(timespec="seconds"), sender, content)
            )
            self.conn.commit()
    
        def classify_message(self, message):
            msg = message.lower()
            if any(w in msg for w in ["code", "python", "script", "corrige"]):
                return "code", 4, "Ajouter une analyse de code plus poussée"
            if any(w in msg for w in ["fichier", "dossier", "ouvrir", "lire"]):
                return "fichiers", 3, "Améliorer l'ouverture et la prévisualisation des fichiers"
            if any(w in msg for w in ["recherche", "web", "internet", "trouve"]):
                return "recherche", 3, "Ajouter un moteur de recherche et de résumé"
            if any(w in msg for w in ["plugin", "module", "extension", "ajouter"]):
                return "extensions", 5, "Créer un système d'installation automatique de plugins"
            if any(w in msg for w in ["erreur", "bug", "panne", "crash", "ne marche pas"]):
                return "diagnostic", 4, "Créer un module de diagnostic plus précis"
            if any(w in msg for w in ["mémoire", "souviens", "historique"]):
                return "memoire", 4, "Ajouter une mémoire conversationnelle plus intelligente"
            return "general", 1, "Améliorer la compréhension générale"
    
        def analyze_and_learn(self, message):
            domain, priority, suggestion = self.classify_message(message)
            self.conn.execute(
                "INSERT INTO needs(ts, message, domain, priority, suggestion) VALUES(?,?,?,?,?)",
                (datetime.now().isoformat(timespec="seconds"), message, domain, priority, suggestion)
            )
            self.conn.execute(
                "INSERT INTO improvements(ts, title, description, score, applied) VALUES(?,?,?,?,0)",
                (datetime.now().isoformat(timespec="seconds"), f"Besoin détecté: {domain}", message, 1)
            )
            self.conn.commit()
            return domain
    
        def top_needs(self, limit=5):
            cur = self.conn.execute("""
                SELECT domain, suggestion, COUNT(*) as cnt, MAX(priority) as maxp
                FROM needs
                WHERE done=0
                GROUP BY domain, suggestion
                ORDER BY cnt DESC, maxp DESC
                LIMIT ?
            """, (limit,))
            return cur.fetchall()
    
        def suggest_next_update(self):
            rows = self.top_needs()
            if not rows:
                return "Je n'ai pas encore assez de données pour proposer une amélioration."
            lines = ["Améliorations suggérées :"]
            for domain, suggestion, cnt, maxp in rows:
                lines.append(f"- {domain} ({cnt} demandes, priorité {maxp}) : {suggestion}")
            return "\n".join(lines)
    
    class PluginManager:
        def __init__(self):
            self.plugins = {}
            PLUGINS_DIR.mkdir(parents=True, exist_ok=True)
    
        def charger_plugins(self):
            self.plugins = {}
            for file in PLUGINS_DIR.glob("*.py"):
                if file.name.startswith("_"):
                    continue
                try:
                    spec = importlib.util.spec_from_file_location(file.stem, file)
                    module = importlib.util.module_from_spec(spec)
                    spec.loader.exec_module(module)
                    if hasattr(module, "INTENTION") and hasattr(module, "executer"):
                        self.plugins[module.INTENTION] = module
                except Exception as e:
                    print(f"Erreur plugin {file.name}: {e}")
    
        def executer(self, intention, message, app):
            module = self.plugins.get(intention)
            if module:
                try:
                    return module.executer(message, app)
                except Exception as e:
                    return f"Erreur plugin {intention}: {e}"
            return None
    
    class ChatIA:
        def __init__(self):
            self.fenetre = tk.Tk()
            self.fenetre.title("MIRA_IA - Assistant Intelligent")
            self.fenetre.geometry("900x700")
    
            self.auto = AutoImprover()
            self.plugin_manager = PluginManager()
            self.plugin_manager.charger_plugins()
            self.historique = []
            self.contexte = {"derniere_intention": None, "derniere_question": None}
    
            self.connaissances = self.charger_connaissances()
    
            self.chat_area = scrolledtext.ScrolledText(
                self.fenetre, width=95, height=32, wrap=tk.WORD,
                font=("Segoe UI", 10), bg="#f8f9fa"
            )
            self.chat_area.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
    
            self.append_system("Bonjour ! Je suis MIRA_IA. Je peux apprendre de vos demandes et proposer des améliorations.")
    
            panel = tk.Frame(self.fenetre)
            panel.pack(fill=tk.X, padx=10, pady=6)
    
            self.entry = tk.Entry(panel, font=("Segoe UI", 10))
            self.entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
            self.entry.bind("<Return>", self.send)
    
            tk.Button(panel, text="Envoyer", command=self.send, bg="#4CAF50", fg="white").pack(side=tk.LEFT, padx=5)
            tk.Button(panel, text="Suggestions", command=self.show_suggestions).pack(side=tk.LEFT, padx=5)
            tk.Button(panel, text="Plugins", command=self.reload_plugins).pack(side=tk.LEFT, padx=5)
            tk.Button(panel, text="Historique", command=self.export_history).pack(side=tk.LEFT, padx=5)
    
        def charger_connaissances(self):
            default = {
                "salutations": {"mots_cles": ["bonjour", "salut", "hello", "hey", "coucou"], "reponses": ["Bonjour !", "Salut !"]},
                "presentation": {"mots_cles": ["qui es-tu", "ton nom", "présente toi"], "reponses": ["Je suis MIRA_IA, un assistant local modulaire."]},
                "remerciements": {"mots_cles": ["merci", "super", "génial"], "reponses": ["Avec plaisir !", "Je vous en prie !"]},
                "au_revoir": {"mots_cles": ["au revoir", "bye", "exit", "quit"], "reponses": ["Au revoir !"], "action": "quitter"},
                "aide": {"mots_cles": ["aide", "commandes", "que peux-tu faire"], "reponses": ["Je peux calculer, rechercher, ouvrir des fichiers, diagnostiquer et charger des plugins."]},
                "calcul": {"mots_cles": ["calcule", "calcul", "combien font", "+", "-", "*", "/"], "reponses": ["Donnez une opération."], "action": "calculer"},
                "recherche": {"mots_cles": ["recherche", "cherche", "trouve", "web", "internet"], "reponses": ["Quel sujet ?"], "action": "recherche_web"},
                "fichier": {"mots_cles": ["ouvre", "lire", "fichier", "dossier"], "reponses": ["Quel fichier ?"], "action": "ouvrir_fichier"},
                "probleme": {"mots_cles": ["erreur", "bug", "problème", "ne marche pas"], "reponses": ["Décrivez le problème."], "action": "resoudre_probleme"},
            }
            if KNOWLEDGE_FILE.exists():
                try:
                    with open(KNOWLEDGE_FILE, "r", encoding="utf-8") as f:
                        return {**default, **json.load(f)}
                except Exception:
                    pass
            KNOWLEDGE_FILE.parent.mkdir(parents=True, exist_ok=True)
            with open(KNOWLEDGE_FILE, "w", encoding="utf-8") as f:
                json.dump(default, f, indent=4, ensure_ascii=False)
            return default
    
        def append(self, sender, message):
            self.chat_area.config(state=tk.NORMAL)
            self.chat_area.insert(tk.END, f"{datetime.now().strftime('%H:%M')} | {sender}: {message}\n")
            self.chat_area.config(state=tk.DISABLED)
            self.chat_area.see(tk.END)
    
        def append_system(self, message):
            self.append("MIRA_IA", message)
    
        def normalize(self, text):
            return re.sub(r"\s+", " ", text.lower().strip())
    
        def send(self, event=None):
            msg = self.entry.get().strip()
            if not msg:
                return
            self.entry.delete(0, tk.END)
            self.append("Toi", msg)
            self.historique.append((datetime.now().isoformat(timespec="seconds"), "Toi", msg))
            self.auto.log_message("Toi", msg)
            self.auto.analyze_and_learn(msg)
            response = self.generate(msg)
            self.append_system(response)
            self.historique.append((datetime.now().isoformat(timespec="seconds"), "MIRA_IA", response))
            self.auto.log_message("MIRA_IA", response)
    
        def generate(self, message):
            msg = self.normalize(message)
    
            if self.contexte["derniere_intention"] == "recherche" and msg not in ["oui", "non"]:
                return self.search_web(message)
    
            intent = self.detect_intent(message)
            if intent:
                info = self.connaissances[intent]
                self.contexte["derniere_intention"] = intent
                self.contexte["derniere_question"] = message
                action = info.get("action")
                if action == "quitter":
                    self.fenetre.after(100, self.fenetre.destroy)
                    return random.choice(info.get("reponses", ["Au revoir !"]))
                if action:
                    return self.execute_action(action, message)
                return random.choice(info.get("reponses", ["Je ne sais pas répondre."]))
    
            plugin_intent = self.detect_plugin_intent(message)
            if plugin_intent:
                rep = self.plugin_manager.executer(plugin_intent, message, self)
                if rep:
                    return rep
    
            self.save_unknown(message)
            return self.fallback_answer(message)
    
        def detect_intent(self, message):
            msg = self.normalize(message)
            best_intent = None
            best_score = 0
            for intent, info in self.connaissances.items():
                score = 0
                for kw in info.get("mots_cles", []):
                    if kw in msg:
                        score += 2
                if score > best_score:
                    best_score = score
                    best_intent = intent
            return best_intent
    
        def detect_plugin_intent(self, message):
            msg = self.normalize(message)
            for intent in self.plugin_manager.plugins:
                if intent in msg:
                    return intent
            return None
    
        def execute_action(self, action, message):
            if action == "calculer":
                return self.calculate(message)
            if action == "recherche_web":
                return self.search_web(message)
            if action == "ouvrir_fichier":
                return self.open_file(message)
            if action == "resoudre_probleme":
                return self.solve_problem(message)
            return "Action non reconnue."
    
        def calculate(self, message):
            expr = self.extract_expression(message)
            if not expr:
                expr = simpledialog.askstring("Calcul", "Entrez une expression:") or ""
            if not expr:
                return "Aucune expression fournie."
            try:
                node = ast.parse(expr, mode="eval")
                return f"Résultat : {self.eval_node(node.body)}"
            except Exception:
                return "Calcul invalide."
    
        def extract_expression(self, message):
            msg = self.normalize(message)
            msg = re.sub(r"^(calcule|calcul|combien font|résous|resous)\s*", "", msg)
            return msg.strip()
    
        def eval_node(self, node):
            if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
                return node.value
            if isinstance(node, ast.BinOp) and type(node.op) in SAFE_OPERATORS:
                return SAFE_OPERATORS[type(node.op)](self.eval_node(node.left), self.eval_node(node.right))
            if isinstance(node, ast.UnaryOp) and type(node.op) in SAFE_OPERATORS:
                return SAFE_OPERATORS[type(node.op)](self.eval_node(node.operand))
            raise ValueError("Expression interdite")
    
        def search_web(self, message):
            msg = self.normalize(message)
            for word in ["recherche", "cherche", "trouve", "web", "internet", "google"]:
                msg = msg.replace(word, "")
            subject = msg.strip()
            if not subject:
                subject = simpledialog.askstring("Recherche web", "Quel sujet ?") or ""
            if not subject:
                return "Recherche annulée."
            webbrowser.open("https://www.google.com/search?q=" + subject.replace(" ", "+"))
            self.contexte["derniere_intention"] = "recherche"
            return f"Recherche lancée pour : {subject}"
    
        def open_file(self, message):
            path_text = re.sub(r"\b(ouvre|lire|fichier|dossier)\b", "", message, flags=re.I).strip().strip('"').strip("'")
            if not path_text:
                path_text = simpledialog.askstring("Fichier", "Chemin complet du fichier:") or ""
            if not path_text:
                return "Aucun fichier indiqué."
            p = Path(path_text)
            if not p.is_absolute():
                # CORRECTION : PROJECT_ROOT calculé automatiquement, plus de "D:\MIRA_Project" en dur
                p = PROJECT_ROOT / path_text
            if not p.exists():
                return f"Le fichier n'existe pas : {p}"
            try:
                if p.suffix.lower() in [".txt", ".py", ".json", ".md", ".log", ".csv"]:
                    return "Voici un aperçu:\n" + "\n".join(p.read_text(encoding="utf-8", errors="replace").splitlines()[:30])
                if os.name == "nt":
                    os.startfile(str(p))
                else:
                    subprocess.run(["xdg-open", str(p)])
                return f"Fichier ouvert : {p}"
            except Exception as e:
                return f"Erreur fichier : {e}"
    
        def solve_problem(self, message):
            msg = self.normalize(message)
            if "python" in msg:
                return "Python : envoyez l'erreur exacte, le code et le résultat attendu."
            if "mira_os" in msg:
                return "MIRA_OS : vérifiez les chemins, les dépendances et les logs."
            return "Décrivez le contexte, le message d'erreur et ce que vous avez essayé."
    
        def fallback_answer(self, message):
            all_keys = list(self.connaissances.keys())
            close = get_close_matches(self.normalize(message), all_keys, n=1, cutoff=0.4)
            if close:
                key = close[0]
                info = self.connaissances[key]
                return random.choice(info.get("reponses", ["Je ne sais pas."]))
            return f"Je n'ai pas compris : '{message}'. Essayez par exemple 'Calcule 5+3' ou 'Recherche Python'."
    
        def save_unknown(self, message):
            HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
            with open(HISTORY_FILE, "a", encoding="utf-8") as f:
                f.write(f"{datetime.now().isoformat(timespec='seconds')} | {message}\n")
    
        def show_suggestions(self):
            msg = self.auto.suggest_next_update()
            messagebox.showinfo("Suggestions d'évolution", msg)
    
        def reload_plugins(self):
            self.plugin_manager.charger_plugins()
            messagebox.showinfo("Plugins", "Plugins rechargés.")
    
        def export_history(self):
            out = BASE_DIR / f"historique_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
            with open(out, "w", encoding="utf-8") as f:
                for ts, sender, msg in self.historique:
                    f.write(f"{ts} | {sender}: {msg}\n")
            self.append_system(f"Historique exporté : {out.name}")
    
        def run(self):
            self.fenetre.mainloop()
    
    if __name__ == "__main__":
        ChatIA().run()
Affichage de 1 message (sur 1 au total)
  • Vous devez être connecté pour répondre à ce sujet.
Retour en haut