File size: 7,981 Bytes
b3dbee1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
API d'Analyse de Sentiment - Serveur Backend Flask
Utilise Hugging Face Transformers (100% gratuit, fonctionne hors ligne)
- Modèle anglais  : distilbert-base-uncased-finetuned-sst-2-english
- Modèle multilingue : tabularisai/multilingual-sentiment-analysis
"""

from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
from transformers import pipeline
import os

# Initialisation de l'application Flask
app = Flask(__name__)

# Autoriser les requêtes cross-origin (depuis le frontend)
CORS(app)

# ─────────────────────────────────────────
# Chargement des modèles Hugging Face
# Les modèles sont téléchargés automatiquement au premier lancement
# puis mis en cache sur le disque pour les prochaines fois
# ─────────────────────────────────────────
print("⏳ Chargement des modèles... (première fois = quelques minutes)")

# Modèle 1 : Anglais uniquement - rapide et précis
model_en = pipeline(
    "sentiment-analysis",
    model="distilbert-base-uncased-finetuned-sst-2-english"
)

# Modèle 2 : Multilingue - supporte arabe, français, anglais, espagnol...
model_multi = pipeline(
    "sentiment-analysis",
    model="tabularisai/multilingual-sentiment-analysis"
)

print("✅ Modèles chargés avec succès !")


# ─────────────────────────────────────────
# Fonction utilitaire : normaliser le résultat Hugging Face
# Les modèles retournent des labels différents, on les unifie ici
# ─────────────────────────────────────────
def normalize_result(hf_result, text_lang):
    """
    Convertit le résultat brut de Hugging Face en format unifié pour le frontend.
    hf_result : liste retournée par le pipeline, ex: [{'label': 'POSITIVE', 'score': 0.99}]
    text_lang : langue du texte pour choisir les labels d'affichage
    """

    label_raw = hf_result[0]["label"].upper()
    score     = round(hf_result[0]["score"] * 100)

    # Mapping des labels vers notre format unifié
    if any(p in label_raw for p in ["POSITIVE", "POS", "5 STARS", "4 STARS"]):
        sentiment = "positive"
        emoji     = "😊"
        pos_score = score
        neg_score = 100 - score
        neu_score = 0
    elif any(p in label_raw for p in ["NEGATIVE", "NEG", "1 STAR", "2 STARS"]):
        sentiment = "negative"
        emoji     = "😠"
        pos_score = 100 - score
        neg_score = score
        neu_score = 0
    else:
        sentiment = "neutral"
        emoji     = "😐"
        pos_score = 30
        neg_score = 30
        neu_score = score

    # Labels multilingues selon la langue de l'interface
    labels = {
        "ar": {"positive": "إيجابي", "negative": "سلبي", "neutral": "محايد", "mixed": "مختلط"},
        "fr": {"positive": "Positif", "negative": "Négatif", "neutral": "Neutre", "mixed": "Mixte"},
        "en": {"positive": "Positive", "negative": "Negative", "neutral": "Neutral", "mixed": "Mixed"},
        "es": {"positive": "Positivo", "negative": "Negativo", "neutral": "Neutral", "mixed": "Mixto"},
        "de": {"positive": "Positiv",  "negative": "Negativ",  "neutral": "Neutral", "mixed": "Gemischt"},
    }

    # Langue d'affichage (par défaut français)
    ui_lang  = text_lang if text_lang in labels else "fr"
    lang_map = labels[ui_lang]

    return {
        "sentiment"     : sentiment,
        "emoji"         : emoji,
        "label"         : lang_map[sentiment],
        "sub"           : f"Score de confiance : {score}%",
        "scores"        : {
            "positive"  : pos_score,
            "negative"  : neg_score,
            "neutral"   : neu_score
        },
        "analysis"      : f"Le modèle a détecté un sentiment {lang_map[sentiment].lower()} "
                          f"avec un score de confiance de {score}%. "
                          f"Ce résultat est basé sur l'analyse locale via Hugging Face Transformers.",
        "keywords"      : [],   # Hugging Face ne retourne pas de mots-clés
        "business"      : f"Un sentiment {lang_map[sentiment].lower()} peut être utilisé "
                          f"pour prioriser les retours clients et adapter la stratégie commerciale.",
        "detected_lang" : text_lang if text_lang != "auto" else "Détection automatique"
    }


# ─────────────────────────────────────────
# Route principale : affiche la page HTML
# ─────────────────────────────────────────
@app.route("/")
def index():
    return render_template("index.html")


# ─────────────────────────────────────────
# Route API : analyse du sentiment
# Méthode : POST
# Corps attendu : { "text": "...", "text_lang": "...", "model": "en"|"multi" }
# ─────────────────────────────────────────
@app.route("/api/analyze", methods=["POST"])
def analyze():
    # Récupération des données JSON envoyées par le frontend
    data = request.get_json()

    # Vérification que le texte est bien présent
    if not data or "text" not in data:
        return jsonify({"error": "Aucun texte fourni"}), 400

    # Extraction des paramètres
    text       = data.get("text", "").strip()
    text_lang  = data.get("text_lang", "auto")
    model_type = data.get("model", "multi")  # "en" ou "multi"

    # Vérification que le texte n'est pas vide
    if not text:
        return jsonify({"error": "Le texte est vide"}), 400

    # Limitation de la taille du texte (les modèles ont une limite de tokens)
    if len(text) > 512:
        text = text[:512]  # On tronque silencieusement

    try:
        # Choix du modèle selon le paramètre reçu
        if model_type == "en":
            # Modèle anglais : rapide, très précis pour l'anglais
            hf_result = model_en(text)
        else:
            # Modèle multilingue : supporte arabe, français, espagnol, etc.
            hf_result = model_multi(text)

        # Normalisation et formatage du résultat
        result = normalize_result(hf_result, text_lang)

        # Ajout du modèle utilisé dans la réponse (utile pour le debug)
        result["model_used"] = "English (DistilBERT)" if model_type == "en" else "Multilingue"

        return jsonify(result)

    except Exception as e:
        return jsonify({"error": str(e)}), 500


# ─────────────────────────────────────────
# Route API : vérification de l'état du serveur
# ─────────────────────────────────────────
@app.route("/api/health", methods=["GET"])
def health():
    return jsonify({
        "status"  : "ok",
        "models"  : {
            "english"     : "distilbert-base-uncased-finetuned-sst-2-english",
            "multilingual": "tabularisai/multilingual-sentiment-analysis"
        }
    })


# ─────────────────────────────────────────
# Point d'entrée : démarrage du serveur
# ─────────────────────────────────────────
if __name__ == "__main__":
    port  = int(os.environ.get("PORT", 5000))
    debug = os.environ.get("FLASK_DEBUG", "false").lower() == "true"
    print(f"🚀 Serveur démarré sur http://localhost:{port}")
    app.run(host="0.0.0.0", port=port, debug=debug)