import os, random import torch import pandas as pd from flask import Flask, render_template, request, jsonify from sentence_transformers import SentenceTransformer, util import gradio as gr BASE_DIR = os.path.abspath(os.path.dirname(__file__)) app = Flask( __name__, template_folder=os.path.join(BASE_DIR, "templates"), static_folder=os.path.join(BASE_DIR, "static") ) CSV_DATA = "dataset_2026.csv" EMB_FILE = "embeddings_questions.pt" TOP_K_RECOMMANDATIONS = 5 print("🔄 Chargement du modèle...") try: model = SentenceTransformer( "OrdalieTech/Solon-embeddings-mini-beta-1.1", device="cpu", trust_remote_code=True ) print("✓ Modèle principal (Solon) chargé") except Exception as e: print(f"⚠️ Échec du modèle principal (probablement dû à la version Hugging Face Hub): {e}") print("🔄 Chargement du modèle de secours (paraphrase-multilingual)...") model = SentenceTransformer( "paraphrase-multilingual-MiniLM-L12-v2", device="cpu" ) print("✓ Modèle de secours chargé avec succès !") df_cache = None def load_data(): global df_cache if df_cache is None: try: # We enforce reading the Latin-1 CSV using ';' as sep. df_cache = pd.read_csv(CSV_DATA, sep=";", encoding="latin-1") df_cache.columns = df_cache.columns.str.strip() except: # Fallback if somehow it's utf-8 with comma df_cache = pd.read_csv(CSV_DATA, sep=None, engine='python', encoding="utf-8") df_cache.columns = df_cache.columns.str.strip() return df_cache def load_or_create_embeddings(df): if os.path.exists(EMB_FILE): emb = torch.load(EMB_FILE, map_location="cpu") if emb.shape[0] == len(df) and emb.shape[1] == model.get_sentence_embedding_dimension(): return emb print("⚠️ Dimensions embeddings incorrectes, recréation...") print("🔨 Création embeddings...") questions = df["Question"].astype(str).tolist() emb = model.encode( questions, convert_to_tensor=True, normalize_embeddings=True ) torch.save(emb, EMB_FILE) return emb def enrich_message(base): return random.choice([ f"Bonne question 🙂\n\n{base}", f"Voici la réponse détaillée :\n\n{base}", f"Voici ce que j'ai trouvé :\n\n{base}", base ]) def process_question(question): df = load_data() emb_base = load_or_create_embeddings(df) emb_q = model.encode(question, convert_to_tensor=True, normalize_embeddings=True) scores = util.pytorch_cos_sim(emb_q, emb_base)[0] best_idx = torch.argmax(scores).item() confidence = int(scores[best_idx].item() * 100) if confidence < 40: recs = df["Question"].sample(min(TOP_K_RECOMMANDATIONS, len(df))).tolist() return { "response": "Je ne suis pas sûr", "confidence": confidence, "matched": "—", "intent": "Inconnu", "recs": recs, "service": None, "lat": None, "lon": None } row = df.iloc[best_idx] service_val = str(row["Service"]).strip() if pd.notna(row["Service"]) else None if service_val and service_val.lower() in ['nan', 'none', 'null', '']: service_val = None link_val = str(row["ServiceLink"]).strip() if pd.notna(row["ServiceLink"]) else None if link_val and link_val.lower() in ['nan', 'none', 'null', '']: link_val = None try: raw_lat = str(row["Latitude"]).replace(',', '.') raw_lon = str(row["Longitude"]).replace(',', '.') lat_val = float(raw_lat) lon_val = float(raw_lon) except Exception: lat_val = None lon_val = None return { "response": enrich_message(row["Response"]), "confidence": confidence, "matched": row["Question"], "intent": row["Intent"], "recs": [], "service": service_val, "link": link_val, "lat": lat_val, "lon": lon_val } @app.route("/") def index(): return render_template("index.html") @app.route("/ask", methods=["POST"]) def ask(): return jsonify(process_question(request.json.get("question", ""))) @app.route("/api/services", methods=["GET"]) def get_services(): df = load_data() valid_df = df.dropna(subset=["Service", "Latitude", "Longitude"]) services = [] seen = set() for _, row in valid_df.iterrows(): try: name = str(row["Service"]).strip() lat = float(str(row["Latitude"]).replace(',', '.')) lon = float(str(row["Longitude"]).replace(',', '.')) if name and name not in seen: services.append({"service": name, "lat": lat, "lon": lon}) seen.add(name) except Exception: pass return jsonify({"status": "success", "services": services}) # ============================== # HUGGING FACE SPACES (GRADIO) # ============================== def gradio_chat(message, history): return process_question(message)["response"] iface = gr.ChatInterface( fn=gradio_chat, title="AskLaQ Assistant" ) if __name__ == "__main__": print("🚀 Serveur Flask lancé sur http://127.0.0.1:7860") app.run(host="0.0.0.0", port=7860, debug=True, use_reloader=False)