Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,16 +1,9 @@
|
|
1 |
import gradio as gr
|
2 |
-
import
|
3 |
-
from transformers import pipeline as hf_pipeline
|
|
|
4 |
|
5 |
-
#
|
6 |
-
sst_classifier = hf_pipeline(
|
7 |
-
"text-classification",
|
8 |
-
model="distilbert-base-uncased-finetuned-sst-2-english",
|
9 |
-
top_k=1,
|
10 |
-
truncation=True
|
11 |
-
)
|
12 |
-
|
13 |
-
# Load emotion classifier
|
14 |
emotion_pipeline = hf_pipeline(
|
15 |
"text-classification",
|
16 |
model="j-hartmann/emotion-english-distilroberta-base",
|
@@ -18,98 +11,95 @@ emotion_pipeline = hf_pipeline(
|
|
18 |
truncation=True
|
19 |
)
|
20 |
|
21 |
-
# Lexicon rules
|
22 |
-
negations = {"not", "never", "no", "none", "nobody", "nothing", "neither", "nowhere", "hardly", "scarcely", "barely"}
|
23 |
-
amplifiers = {"very", "really", "extremely", "so", "totally", "completely", "absolutely", "utterly", "super"}
|
24 |
-
softeners = {"slightly", "somewhat", "a bit", "a little", "mildly", "fairly", "kind of"}
|
25 |
-
|
26 |
-
def preprocess_sentiment_text(text):
|
27 |
-
words = text.lower().split()
|
28 |
-
modified = []
|
29 |
-
negate = False
|
30 |
-
|
31 |
-
for word in words:
|
32 |
-
stripped = re.sub(r'\W+', '', word)
|
33 |
-
|
34 |
-
if stripped in negations:
|
35 |
-
negate = True
|
36 |
-
modified.append("<NEG>")
|
37 |
-
continue
|
38 |
-
if stripped in amplifiers:
|
39 |
-
modified.append(f"<AMP>{word}")
|
40 |
-
continue
|
41 |
-
if stripped in softeners:
|
42 |
-
modified.append(f"<SOFT>{word}")
|
43 |
-
continue
|
44 |
-
if negate:
|
45 |
-
modified.append(f"<NEG>{word}")
|
46 |
-
negate = False
|
47 |
-
else:
|
48 |
-
modified.append(word)
|
49 |
-
|
50 |
-
return " ".join(modified)
|
51 |
-
|
52 |
def get_emotion_profile(text):
|
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 |
def analyze_message(text):
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
|
96 |
-
# No abuse patterns yet
|
97 |
-
patterns = []
|
98 |
-
tone_tag = get_emotional_tone_tag(emotions, sentiment_label, patterns)
|
99 |
-
tone_output = tone_tag if tone_tag else "None detected"
|
100 |
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
f"🔍 Tone Tag: {tone_output}"
|
105 |
-
)
|
106 |
|
107 |
iface = gr.Interface(
|
108 |
-
fn=
|
109 |
-
inputs=
|
110 |
-
outputs="
|
111 |
-
title="Tether
|
112 |
-
description="
|
113 |
)
|
114 |
|
115 |
-
|
|
|
|
1 |
import gradio as gr
|
2 |
+
import torch
|
3 |
+
from transformers import pipeline as hf_pipeline, AutoModelForSequenceClassification, AutoTokenizer
|
4 |
+
from motif_tagging import detect_motifs
|
5 |
|
6 |
+
# ——— 1) Emotion Pipeline ————————————————————————————————————————————————
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
emotion_pipeline = hf_pipeline(
|
8 |
"text-classification",
|
9 |
model="j-hartmann/emotion-english-distilroberta-base",
|
|
|
11 |
truncation=True
|
12 |
)
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
def get_emotion_profile(text):
|
15 |
+
"""
|
16 |
+
Returns a dict of { emotion_label: score } for the input text.
|
17 |
+
"""
|
18 |
+
results = emotion_pipeline(text)
|
19 |
+
# some pipelines return [[…]]
|
20 |
+
if isinstance(results, list) and isinstance(results[0], list):
|
21 |
+
results = results[0]
|
22 |
+
return {r["label"].lower(): round(r["score"], 3) for r in results}
|
23 |
+
|
24 |
+
|
25 |
+
# ——— 2) Abuse-patterns Model ——————————————————————————————————————————————
|
26 |
+
model_name = "SamanthaStorm/tether-multilabel-v3"
|
27 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_name)
|
28 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)
|
29 |
+
|
30 |
+
LABELS = [
|
31 |
+
"blame shifting", "contradictory statements", "control", "dismissiveness",
|
32 |
+
"gaslighting", "guilt tripping", "insults", "obscure language",
|
33 |
+
"projection", "recovery phase", "threat"
|
34 |
+
]
|
35 |
+
|
36 |
+
THRESHOLDS = {
|
37 |
+
"blame shifting": 0.28, "contradictory statements": 0.27, "control": 0.08, "dismissiveness": 0.32,
|
38 |
+
"gaslighting": 0.27, "guilt tripping": 0.31, "insults": 0.10, "obscure language": 0.55,
|
39 |
+
"projection": 0.09, "recovery phase": 0.33, "threat": 0.15
|
40 |
+
}
|
41 |
+
|
42 |
+
|
43 |
+
# ——— 3) Single-message analysis ——————————————————————————————————————————————
|
|
|
44 |
def analyze_message(text):
|
45 |
+
"""
|
46 |
+
Runs motif detection, emotion profiling, and the abuse-pattern classifier.
|
47 |
+
Returns a dict with:
|
48 |
+
- matched_phrases: list of raw regex hits
|
49 |
+
- emotion_profile: { emotion: score }
|
50 |
+
- active_patterns: [ labels above their threshold ]
|
51 |
+
"""
|
52 |
+
motif_hits, matched_phrases = detect_motifs(text)
|
53 |
+
emotion_profile = get_emotion_profile(text)
|
54 |
+
|
55 |
+
# get raw model scores
|
56 |
+
toks = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
57 |
+
with torch.no_grad():
|
58 |
+
logits = model(**toks).logits.squeeze(0)
|
59 |
+
scores = torch.sigmoid(logits).cpu().numpy()
|
60 |
+
|
61 |
+
# pick up all labels whose score >= threshold
|
62 |
+
active = [lab for lab, sc in zip(LABELS, scores) if sc >= THRESHOLDS[lab]]
|
63 |
+
|
64 |
+
return {
|
65 |
+
"matched_phrases": matched_phrases,
|
66 |
+
"emotion_profile": emotion_profile,
|
67 |
+
"active_patterns": active
|
68 |
+
}
|
69 |
+
|
70 |
+
|
71 |
+
# ——— 4) Composite wrapper (for multiple inputs) ———————————————————————————————————
|
72 |
+
def analyze_composite(*texts):
|
73 |
+
"""
|
74 |
+
Accept multiple text inputs, run analyze_message on each, and
|
75 |
+
return a single formatted string.
|
76 |
+
"""
|
77 |
+
outputs = []
|
78 |
+
for idx, txt in enumerate(texts, start=1):
|
79 |
+
if not txt:
|
80 |
+
continue
|
81 |
+
r = analyze_message(txt)
|
82 |
+
block = (
|
83 |
+
f"── Message {idx} ──\n"
|
84 |
+
f"Matched Phrases: {r['matched_phrases']}\n"
|
85 |
+
f"Emotion Profile: {r['emotion_profile']}\n"
|
86 |
+
f"Active Patterns: {r['active_patterns']}\n"
|
87 |
+
)
|
88 |
+
outputs.append(block)
|
89 |
+
return "\n".join(outputs) if outputs else "Please enter at least one message."
|
90 |
|
|
|
|
|
|
|
|
|
91 |
|
92 |
+
# ——— 5) Gradio interface ————————————————————————————————————————————————
|
93 |
+
# adjust how many message inputs you want here:
|
94 |
+
message_inputs = [gr.Textbox(label=f"Message {i+1}") for i in range(3)]
|
|
|
|
|
95 |
|
96 |
iface = gr.Interface(
|
97 |
+
fn=analyze_composite,
|
98 |
+
inputs=message_inputs,
|
99 |
+
outputs=gr.Textbox(label="Analysis"),
|
100 |
+
title="Tether Analyzer (no abuse-score / no DARVO)",
|
101 |
+
description="Detects motifs, emotions, and active abuse-patterns only."
|
102 |
)
|
103 |
|
104 |
+
if __name__ == "__main__":
|
105 |
+
iface.launch()
|