TetherSST / app.py
SamanthaStorm's picture
Update app.py
3624f82 verified
raw
history blame
7.32 kB
import gradio as gr
import torch
from transformers import pipeline as hf_pipeline, AutoModelForSequenceClassification, AutoTokenizer
from PIL import Image
import pytesseract
import io
# ——— 1) Emotion Pipeline ————————————————————————————————————————————————
emotion_pipeline = hf_pipeline(
"text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
top_k=None,
truncation=True
)
def get_emotion_profile(text):
results = emotion_pipeline(text)
if isinstance(results, list) and isinstance(results[0], list):
results = results[0]
return {r["label"].lower(): round(r["score"], 3) for r in results}
# ——— 2) Abuse-patterns Model ——————————————————————————————————————————————
model_name = "SamanthaStorm/tether-multilabel-v3"
model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)
LABELS = [
"blame shifting", "contradictory statements", "control", "dismissiveness",
"gaslighting", "guilt tripping", "insults", "obscure language",
"projection", "recovery phase", "threat"
]
THRESHOLDS = {
"blame shifting": 0.28, "contradictory statements": 0.27, "control": 0.08, "dismissiveness": 0.32,
"gaslighting": 0.27, "guilt tripping": 0.31, "insults": 0.10, "obscure language": 0.55,
"projection": 0.09, "recovery phase": 0.33, "threat": 0.15
}
# ——— 3) Emotional-tone tagging (detailed categories) —————————————————————————————
def get_emotional_tone_tag(emotion_profile, patterns):
# unpack emotion scores
sadness = emotion_profile.get("sadness", 0)
joy = emotion_profile.get("joy", 0)
neutral = emotion_profile.get("neutral", 0)
disgust = emotion_profile.get("disgust", 0)
anger = emotion_profile.get("anger", 0)
fear = emotion_profile.get("fear", 0)
# 1. Performative Regret
if (
sadness > 0.4 and
any(p in patterns for p in ["blame shifting", "guilt tripping", "recovery phase"])
):
return "performative regret"
# 2. Coercive Warmth
if (
(joy > 0.3 or sadness > 0.4) and
any(p in patterns for p in ["control", "gaslighting"])
):
return "coercive warmth"
# 3. Cold Invalidation
if (
(neutral + disgust) > 0.5 and
any(p in patterns for p in ["dismissiveness", "projection", "obscure language"])
):
return "cold invalidation"
# 4. Genuine Vulnerability
if (
(sadness + fear) > 0.5 and
all(p == "recovery phase" for p in patterns)
):
return "genuine vulnerability"
# 5. Emotional Threat
if (
(anger + disgust) > 0.5 and
any(p in patterns for p in ["control", "threat", "insults", "dismissiveness"])
):
return "emotional threat"
# 6. Weaponized Sadness
if (
sadness > 0.6 and
any(p in patterns for p in ["guilt tripping", "projection"])
):
return "weaponized sadness"
# 7. Toxic Resignation
if (
neutral > 0.5 and
any(p in patterns for p in ["dismissiveness", "obscure language"])
):
return "toxic resignation"
# 8. Indignant Reproach
if (
anger > 0.5 and
any(p in patterns for p in ["guilt tripping","contradictory statements"]
):
return "indignant reproach"
if (
anger > 0.6 and patterns
):
return "confrontational"
# 8. Passive Aggression
if neutral > 0.6 and any(p in patterns for p in ["dismissiveness","projection"]):
return "passive aggression"
# 9. Sarcastic Mockery
if joy > 0.3 and "insults" in patterns:
return "sarcastic mockery"
return None
# ——— 4) Single-message analysis —————————————————————————————————————————————
def analyze_message(text):
# 1) emotion profiling
emotion_profile = get_emotion_profile(text)
# 2) abuse-pattern classification
toks = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
logits = model(**toks).logits.squeeze(0)
scores = torch.sigmoid(logits).cpu().numpy()
# 3) identify active patterns
active_patterns = [
label for label, prob in zip(LABELS, scores)
if prob >= THRESHOLDS[label]
]
# 4) add recovery-phase on apology keywords
low = text.lower()
if any(k in low for k in ["sorry", "apolog", "forgive"]):
if "recovery phase" not in active_patterns:
active_patterns.append("recovery phase")
# 5) tone tagging with detailed rules
tone_tag = get_emotional_tone_tag(emotion_profile, active_patterns)
return {
"emotion_profile": emotion_profile,
"active_patterns": active_patterns,
"tone_tag": tone_tag
}
# ——— 5) Composite wrapper (handles uploads + text boxes) ——————————————————————————
def analyze_composite(uploaded_file, *texts):
outputs = []
# file upload
if uploaded_file is not None:
raw = uploaded_file.read()
name = uploaded_file.name.lower()
if name.endswith((".png", ".jpg", ".jpeg", ".tiff", ".bmp", ".gif")):
img = Image.open(io.BytesIO(raw))
content = pytesseract.image_to_string(img)
else:
try:
content = raw.decode("utf-8")
except UnicodeDecodeError:
content = raw.decode("latin-1")
r = analyze_message(content)
outputs.append(
"── Uploaded File ──\n"
f"Emotion Profile : {r['emotion_profile']}\n"
f"Active Patterns : {r['active_patterns']}\n"
f"Emotional Tone : {r['tone_tag']}\n"
)
# text inputs
for idx, txt in enumerate(texts, start=1):
if not txt:
continue
r = analyze_message(txt)
outputs.append(
f"── Message {idx} ──\n"
f"Emotion Profile : {r['emotion_profile']}\n"
f"Active Patterns : {r['active_patterns']}\n"
f"Emotional Tone : {r['tone_tag']}\n"
)
if not outputs:
return "Please enter at least one message."
return "\n".join(outputs)
# ——— 6) Gradio interface ————————————————————————————————————————————————
message_inputs = [gr.Textbox(label=f"Message {i+1}") for i in range(3)]
iface = gr.Interface(
fn=analyze_composite,
inputs=[
gr.File(file_types=[".txt", ".png", ".jpg", ".jpeg"], label="Upload text or image")
] + message_inputs,
outputs=gr.Textbox(label="Analysis"),
title="Tether Analyzer (detailed tone tags)",
description="Emotion profiling, pattern tags, and nuanced tone categories—no abuse score or DARVO."
)
if __name__ == "__main__":
iface.launch()