Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,13 +1,10 @@
|
|
1 |
-
import easyocr
|
2 |
-
import numpy as np
|
3 |
-
|
4 |
-
# create a reader once
|
5 |
-
ocr_reader = easyocr.Reader(["en"], gpu=False)
|
6 |
import gradio as gr
|
7 |
import torch
|
8 |
from transformers import pipeline as hf_pipeline, AutoModelForSequenceClassification, AutoTokenizer
|
9 |
from PIL import Image
|
10 |
import io
|
|
|
|
|
11 |
|
12 |
# ——— 1) Emotion Pipeline ————————————————————————————————————————————————
|
13 |
emotion_pipeline = hf_pipeline(
|
@@ -18,11 +15,7 @@ emotion_pipeline = hf_pipeline(
|
|
18 |
)
|
19 |
|
20 |
def get_emotion_profile(text):
|
21 |
-
"""
|
22 |
-
Returns a dict of emotion scores for the input text.
|
23 |
-
"""
|
24 |
results = emotion_pipeline(text)
|
25 |
-
# Some pipelines return a list of lists
|
26 |
if isinstance(results, list) and isinstance(results[0], list):
|
27 |
results = results[0]
|
28 |
return {r["label"].lower(): round(r["score"], 3) for r in results}
|
@@ -55,11 +48,15 @@ THRESHOLDS = {
|
|
55 |
"threat": 0.15
|
56 |
}
|
57 |
|
58 |
-
# ——— 3)
|
|
|
|
|
|
|
59 |
def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
60 |
"""
|
61 |
Assigns one of 18 nuanced tone categories based on emotion scores, patterns, and text.
|
62 |
"""
|
|
|
63 |
sadness = emotion_profile.get("sadness", 0)
|
64 |
joy = emotion_profile.get("joy", 0)
|
65 |
neutral = emotion_profile.get("neutral", 0)
|
@@ -68,62 +65,40 @@ def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
|
68 |
fear = emotion_profile.get("fear", 0)
|
69 |
surprise = emotion_profile.get("surprise", 0)
|
70 |
|
71 |
-
|
72 |
-
if
|
73 |
return "supportive"
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
):
|
78 |
return "performative regret"
|
79 |
|
80 |
# 2. Coercive Warmth
|
81 |
-
if (
|
82 |
-
(joy > 0.3 or sadness > 0.4) and
|
83 |
-
any(p in patterns for p in ["control", "gaslighting"])
|
84 |
-
):
|
85 |
return "coercive warmth"
|
86 |
|
87 |
# 3. Cold Invalidation
|
88 |
-
if (
|
89 |
-
(neutral + disgust) > 0.5 and
|
90 |
-
any(p in patterns for p in ["dismissiveness", "projection", "obscure language"])
|
91 |
-
):
|
92 |
return "cold invalidation"
|
93 |
|
94 |
# 4. Genuine Vulnerability
|
95 |
-
if (
|
96 |
-
(sadness + fear) > 0.5 and
|
97 |
-
all(p == "recovery phase" for p in patterns)
|
98 |
-
):
|
99 |
return "genuine vulnerability"
|
100 |
|
101 |
# 5. Emotional Threat
|
102 |
-
if (
|
103 |
-
(anger + disgust) > 0.5 and
|
104 |
-
any(p in patterns for p in ["control", "threat", "insults", "dismissiveness"])
|
105 |
-
):
|
106 |
return "emotional threat"
|
107 |
|
108 |
# 6. Weaponized Sadness
|
109 |
-
if (
|
110 |
-
sadness > 0.6 and
|
111 |
-
any(p in patterns for p in ["guilt tripping", "projection"])
|
112 |
-
):
|
113 |
return "weaponized sadness"
|
114 |
|
115 |
# 7. Toxic Resignation
|
116 |
-
if (
|
117 |
-
neutral > 0.5 and
|
118 |
-
any(p in patterns for p in ["dismissiveness", "obscure language"])
|
119 |
-
):
|
120 |
return "toxic resignation"
|
121 |
|
122 |
# 8. Indignant Reproach
|
123 |
-
if (
|
124 |
-
anger > 0.5 and
|
125 |
-
any(p in patterns for p in ["guilt tripping", "contradictory statements"])
|
126 |
-
):
|
127 |
return "indignant reproach"
|
128 |
|
129 |
# 9. Confrontational
|
@@ -131,10 +106,7 @@ def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
|
131 |
return "confrontational"
|
132 |
|
133 |
# 10. Passive Aggression
|
134 |
-
if (
|
135 |
-
neutral > 0.6 and
|
136 |
-
any(p in patterns for p in ["dismissiveness", "projection"])
|
137 |
-
):
|
138 |
return "passive aggression"
|
139 |
|
140 |
# 11. Sarcastic Mockery
|
@@ -146,11 +118,7 @@ def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
|
146 |
return "menacing threat"
|
147 |
|
148 |
# 13. Pleading Concern
|
149 |
-
if (
|
150 |
-
sadness > 0.3 and
|
151 |
-
any(k in text_lower for k in APOLOGY_KEYWORDS) and
|
152 |
-
not patterns
|
153 |
-
):
|
154 |
return "pleading concern"
|
155 |
|
156 |
# 14. Fear-mongering
|
@@ -175,43 +143,30 @@ def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
|
175 |
|
176 |
return None
|
177 |
|
178 |
-
# ———
|
179 |
def analyze_message(text):
|
180 |
text_lower = text.lower()
|
181 |
-
# 1) Emotion
|
182 |
emotion_profile = get_emotion_profile(text)
|
183 |
-
# 2) Patterns
|
184 |
toks = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
185 |
with torch.no_grad():
|
186 |
logits = model(**toks).logits.squeeze(0)
|
187 |
scores = torch.sigmoid(logits).cpu().numpy()
|
188 |
-
active_patterns = [
|
189 |
-
# append recovery-phase if apology
|
190 |
if any(k in text_lower for k in APOLOGY_KEYWORDS) and "recovery phase" not in active_patterns:
|
191 |
active_patterns.append("recovery phase")
|
192 |
-
# 3) Tone
|
193 |
tone_tag = get_emotional_tone_tag(emotion_profile, active_patterns, text_lower)
|
194 |
-
return {
|
195 |
-
"emotion_profile": emotion_profile,
|
196 |
-
"active_patterns": active_patterns,
|
197 |
-
"tone_tag": tone_tag
|
198 |
-
}
|
199 |
|
200 |
-
# ———
|
201 |
def analyze_composite(uploaded_file, *texts):
|
202 |
outputs = []
|
203 |
-
|
204 |
-
# 1) Handle an uploaded file
|
205 |
if uploaded_file is not None:
|
206 |
-
# uploaded_file may be a file-like object or just a path string
|
207 |
try:
|
208 |
raw = uploaded_file.read()
|
209 |
except Exception:
|
210 |
-
# fall back to opening the path
|
211 |
with open(uploaded_file, "rb") as f:
|
212 |
raw = f.read()
|
213 |
|
214 |
-
# get the filename (or just lowercase the string if it's a path)
|
215 |
name = (
|
216 |
uploaded_file.name.lower() if hasattr(uploaded_file, "name") else uploaded_file.lower()
|
217 |
)
|
@@ -249,19 +204,13 @@ def analyze_composite(uploaded_file, *texts):
|
|
249 |
|
250 |
# ——— 7) Gradio interface ———————————————————————————————————————————————
|
251 |
message_inputs = [gr.Textbox(label="Message")]
|
252 |
-
|
253 |
iface = gr.Interface(
|
254 |
fn=analyze_composite,
|
255 |
-
inputs=[
|
256 |
-
gr.File(
|
257 |
-
file_types=[".txt", ".png", ".jpg", ".jpeg"],
|
258 |
-
label="Upload text or image"
|
259 |
-
)
|
260 |
-
] + message_inputs, # <— here the list closes, then we concatenate
|
261 |
outputs=gr.Textbox(label="Analysis"),
|
262 |
title="Tether Analyzer (extended tone tags)",
|
263 |
description="Emotion profiling, pattern tags, and a wide set of nuanced tone categories—no abuse score or DARVO."
|
264 |
)
|
265 |
|
266 |
if __name__ == "__main__":
|
267 |
-
iface.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import torch
|
3 |
from transformers import pipeline as hf_pipeline, AutoModelForSequenceClassification, AutoTokenizer
|
4 |
from PIL import Image
|
5 |
import io
|
6 |
+
import easyocr
|
7 |
+
import numpy as np
|
8 |
|
9 |
# ——— 1) Emotion Pipeline ————————————————————————————————————————————————
|
10 |
emotion_pipeline = hf_pipeline(
|
|
|
15 |
)
|
16 |
|
17 |
def get_emotion_profile(text):
|
|
|
|
|
|
|
18 |
results = emotion_pipeline(text)
|
|
|
19 |
if isinstance(results, list) and isinstance(results[0], list):
|
20 |
results = results[0]
|
21 |
return {r["label"].lower(): round(r["score"], 3) for r in results}
|
|
|
48 |
"threat": 0.15
|
49 |
}
|
50 |
|
51 |
+
# ——— 3) Initialize EasyOCR reader ————————————————————————————————————————————
|
52 |
+
ocr_reader = easyocr.Reader(["en"], gpu=False)
|
53 |
+
|
54 |
+
# ——— 4) Emotional-Tone Tagging —————————————————————————————————————————————
|
55 |
def get_emotional_tone_tag(emotion_profile, patterns, text_lower):
|
56 |
"""
|
57 |
Assigns one of 18 nuanced tone categories based on emotion scores, patterns, and text.
|
58 |
"""
|
59 |
+
# unpack all emotion scores before any rules
|
60 |
sadness = emotion_profile.get("sadness", 0)
|
61 |
joy = emotion_profile.get("joy", 0)
|
62 |
neutral = emotion_profile.get("neutral", 0)
|
|
|
65 |
fear = emotion_profile.get("fear", 0)
|
66 |
surprise = emotion_profile.get("surprise", 0)
|
67 |
|
68 |
+
# 0. Support override
|
69 |
+
if any(k in text_lower for k in ["support", "hope", "grace"]):
|
70 |
return "supportive"
|
71 |
+
|
72 |
+
# 1. Performative Regret
|
73 |
+
if sadness > 0.4 and any(p in patterns for p in ["blame shifting", "guilt tripping", "recovery phase"]):
|
|
|
74 |
return "performative regret"
|
75 |
|
76 |
# 2. Coercive Warmth
|
77 |
+
if (joy > 0.3 or sadness > 0.4) and any(p in patterns for p in ["control", "gaslighting"]):
|
|
|
|
|
|
|
78 |
return "coercive warmth"
|
79 |
|
80 |
# 3. Cold Invalidation
|
81 |
+
if (neutral + disgust) > 0.5 and any(p in patterns for p in ["dismissiveness", "projection", "obscure language"]):
|
|
|
|
|
|
|
82 |
return "cold invalidation"
|
83 |
|
84 |
# 4. Genuine Vulnerability
|
85 |
+
if (sadness + fear) > 0.5 and all(p == "recovery phase" for p in patterns):
|
|
|
|
|
|
|
86 |
return "genuine vulnerability"
|
87 |
|
88 |
# 5. Emotional Threat
|
89 |
+
if (anger + disgust) > 0.5 and any(p in patterns for p in ["control", "threat", "insults", "dismissiveness"]):
|
|
|
|
|
|
|
90 |
return "emotional threat"
|
91 |
|
92 |
# 6. Weaponized Sadness
|
93 |
+
if sadness > 0.6 and any(p in patterns for p in ["guilt tripping", "projection"]):
|
|
|
|
|
|
|
94 |
return "weaponized sadness"
|
95 |
|
96 |
# 7. Toxic Resignation
|
97 |
+
if neutral > 0.5 and any(p in patterns for p in ["dismissiveness", "obscure language"]):
|
|
|
|
|
|
|
98 |
return "toxic resignation"
|
99 |
|
100 |
# 8. Indignant Reproach
|
101 |
+
if anger > 0.5 and any(p in patterns for p in ["guilt tripping", "contradictory statements"]):
|
|
|
|
|
|
|
102 |
return "indignant reproach"
|
103 |
|
104 |
# 9. Confrontational
|
|
|
106 |
return "confrontational"
|
107 |
|
108 |
# 10. Passive Aggression
|
109 |
+
if neutral > 0.6 and any(p in patterns for p in ["dismissiveness", "projection"]):
|
|
|
|
|
|
|
110 |
return "passive aggression"
|
111 |
|
112 |
# 11. Sarcastic Mockery
|
|
|
118 |
return "menacing threat"
|
119 |
|
120 |
# 13. Pleading Concern
|
121 |
+
if sadness > 0.3 and any(k in text_lower for k in APOLOGY_KEYWORDS) and not patterns:
|
|
|
|
|
|
|
|
|
122 |
return "pleading concern"
|
123 |
|
124 |
# 14. Fear-mongering
|
|
|
143 |
|
144 |
return None
|
145 |
|
146 |
+
# ——— 5) Single message analysis ———————————————————————————————————————————
|
147 |
def analyze_message(text):
|
148 |
text_lower = text.lower()
|
|
|
149 |
emotion_profile = get_emotion_profile(text)
|
|
|
150 |
toks = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
|
151 |
with torch.no_grad():
|
152 |
logits = model(**toks).logits.squeeze(0)
|
153 |
scores = torch.sigmoid(logits).cpu().numpy()
|
154 |
+
active_patterns = [label for label, prob in zip(LABELS, scores) if prob >= THRESHOLDS[label]]
|
|
|
155 |
if any(k in text_lower for k in APOLOGY_KEYWORDS) and "recovery phase" not in active_patterns:
|
156 |
active_patterns.append("recovery phase")
|
|
|
157 |
tone_tag = get_emotional_tone_tag(emotion_profile, active_patterns, text_lower)
|
158 |
+
return {"emotion_profile": emotion_profile, "active_patterns": active_patterns, "tone_tag": tone_tag}
|
|
|
|
|
|
|
|
|
159 |
|
160 |
+
# ——— 6) Composite wrapper ———————————————————————————————————————————————
|
161 |
def analyze_composite(uploaded_file, *texts):
|
162 |
outputs = []
|
|
|
|
|
163 |
if uploaded_file is not None:
|
|
|
164 |
try:
|
165 |
raw = uploaded_file.read()
|
166 |
except Exception:
|
|
|
167 |
with open(uploaded_file, "rb") as f:
|
168 |
raw = f.read()
|
169 |
|
|
|
170 |
name = (
|
171 |
uploaded_file.name.lower() if hasattr(uploaded_file, "name") else uploaded_file.lower()
|
172 |
)
|
|
|
204 |
|
205 |
# ——— 7) Gradio interface ———————————————————————————————————————————————
|
206 |
message_inputs = [gr.Textbox(label="Message")]
|
|
|
207 |
iface = gr.Interface(
|
208 |
fn=analyze_composite,
|
209 |
+
inputs=[gr.File(file_types=[".txt",".png",".jpg",".jpeg"], label="Upload text or image")] + message_inputs,
|
|
|
|
|
|
|
|
|
|
|
210 |
outputs=gr.Textbox(label="Analysis"),
|
211 |
title="Tether Analyzer (extended tone tags)",
|
212 |
description="Emotion profiling, pattern tags, and a wide set of nuanced tone categories—no abuse score or DARVO."
|
213 |
)
|
214 |
|
215 |
if __name__ == "__main__":
|
216 |
+
iface.launch()
|