File size: 7,218 Bytes
d6e219c
f1948f2
 
2f6ac5d
e032990
b54664e
6153eb8
0ff864f
1dbc865
 
 
e185e86
2f6ac5d
1dbc865
 
 
 
 
 
 
 
 
 
2f6ac5d
 
1dbc865
 
e032990
 
a9d4250
e185e86
1dbc865
 
 
e185e86
 
 
1dbc865
 
 
 
 
 
 
 
 
 
 
e185e86
 
2f6ac5d
1dbc865
 
 
 
 
2f6ac5d
aeed86a
e185e86
1dbc865
 
 
 
 
 
 
 
 
 
 
e185e86
 
d33c30b
1dbc865
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d33c30b
 
 
1dbc865
 
 
 
 
 
 
 
d33c30b
d315105
 
 
 
 
9ab7ab9
d315105
9ab7ab9
 
d315105
 
 
1e3558a
 
9ab7ab9
 
 
 
 
 
 
 
4472a1d
d315105
 
 
 
 
 
 
 
 
 
 
 
 
 
b883fe8
1dbc865
 
d315105
 
1dbc865
cb6b46c
d315105
1dbc865
d315105
 
cb6b46c
d315105
 
 
 
ec5f81e
 
1dbc865
 
 
e032990
 
d315105
 
a28ef35
ab8c96f
1dbc865
d315105
1dbc865
d315105
1dbc865
ab8c96f
4292d1b
1dbc865
9ab7ab9
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
import gradio as gr
import torch
import numpy as np
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from transformers import RobertaForSequenceClassification, RobertaTokenizer
from motif_tagging import detect_motifs
import re

# --- Sentiment Model: T5-based Emotion Classifier ---
sentiment_tokenizer = AutoTokenizer.from_pretrained("mrm8488/t5-base-finetuned-emotion")
sentiment_model = AutoModelForSeq2SeqLM.from_pretrained("mrm8488/t5-base-finetuned-emotion")

EMOTION_TO_SENTIMENT = {
    "joy": "supportive",
    "love": "supportive",
    "surprise": "supportive",
    "neutral": "supportive",
    "sadness": "undermining",
    "anger": "undermining",
    "fear": "undermining",
    "disgust": "undermining",
    "shame": "undermining",
    "guilt": "undermining"
}

# --- Abuse Detection Model ---
model_name = "SamanthaStorm/autotrain-jlpi4-mllvp"
model = RobertaForSequenceClassification.from_pretrained(model_name, trust_remote_code=True)
tokenizer = RobertaTokenizer.from_pretrained(model_name, trust_remote_code=True)

LABELS = [
    "blame shifting", "contradictory statements", "control", "dismissiveness",
    "gaslighting", "guilt tripping", "insults", "obscure language",
    "projection", "recovery phase", "threat"
]

THRESHOLDS = {
    "blame shifting": 0.3,
    "contradictory statements": 0.32,
    "control": 0.48,
    "dismissiveness": 0.45,
    "gaslighting": 0.30,
    "guilt tripping": 0.20,
    "insults": 0.34,
    "obscure language": 0.25,
    "projection": 0.35,
    "recovery phase": 0.25,
    "threat": 0.25
}

PATTERN_WEIGHTS = {
    "gaslighting": 1.3,
    "control": 1.2,
    "dismissiveness": 0.8,
    "blame shifting": 0.8,
    "contradictory statements": 0.75
}

EXPLANATIONS = {
    "blame shifting": "Blame-shifting is when one person redirects responsibility onto someone else to avoid accountability.",
    "contradictory statements": "Contradictory statements confuse the listener by flipping positions or denying previous claims.",
    "control": "Control restricts another person’s autonomy through coercion, manipulation, or threats.",
    "dismissiveness": "Dismissiveness is belittling or disregarding another person’s feelings, needs, or opinions.",
    "gaslighting": "Gaslighting involves making someone question their own reality, memory, or perceptions.",
    "guilt tripping": "Guilt-tripping uses guilt to manipulate someone’s actions or decisions.",
    "insults": "Insults are derogatory or demeaning remarks meant to shame, belittle, or hurt someone.",
    "obscure language": "Obscure language manipulates through complexity, vagueness, or superiority to confuse the other person.",
    "projection": "Projection accuses someone else of the very behaviors or intentions the speaker is exhibiting.",
    "recovery phase": "Recovery phase statements attempt to soothe or reset tension without acknowledging harm or change.",
    "threat": "Threats use fear of harm (physical, emotional, or relational) to control or intimidate someone."
}

RISK_SNIPPETS = {
    "low": (
        "🟢 Risk Level: Low",
        "The language patterns here do not strongly indicate abuse.",
        "Continue to check in with yourself and notice how you feel in response to repeated patterns."
    ),
    "moderate": (
        "⚠️ Risk Level: Moderate to High",
        "This language includes control, guilt, or reversal tactics.",
        "These patterns often lead to emotional confusion and reduced self-trust. Document these messages or talk with someone safe."
    ),
    "high": (
        "🛑 Risk Level: High",
        "Language includes threats or coercive control, which are strong indicators of escalation.",
        "Consider creating a safety plan or contacting a support line. Trust your sense of unease."
    )
}

def generate_risk_snippet(abuse_score, top_label):
    if abuse_score >= 85:
        risk_level = "high"
    elif abuse_score >= 60:
        risk_level = "moderate"
    else:
        risk_level = "low"
    title, summary, advice = RISK_SNIPPETS[risk_level]
    return f"\n\n{title}\n{summary} (Pattern: **{top_label}**)\n💡 {advice}"

# --- Escalation Quiz Questions & Weights ---
ESCALATION_QUESTIONS = [
    ("Partner has access to firearms or weapons", 4),
    ("Partner threatened to kill you", 3),
    ("Partner threatened you with a weapon", 3),
    ("Partner has ever choked you, even if you considered it consensual at the time", 4),
    ("Partner injured or threatened your pet(s)", 3),
    ("Partner has broken your things, punched or kicked walls, or thrown things ", 2),
    ("Partner forced or coerced you into unwanted sexual acts", 3),
    ("Partner threatened to take away your children", 2),
    ("Violence has increased in frequency or severity", 3),
    ("Partner monitors your calls/GPS/social media", 2)
]

def analyze_single_message(text, thresholds, motif_flags):
    inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
    with torch.no_grad():
        outputs = model(**inputs)
    scores = torch.sigmoid(outputs.logits.squeeze(0)).numpy()
    threshold_labels = [label for label, score in zip(LABELS, scores) if score > thresholds[label]]
    top_patterns = sorted([(label, score) for label, score in zip(LABELS, scores)], key=lambda x: x[1], reverse=True)[:2]
    return (np.mean([score for _, score in top_patterns]) * 100, threshold_labels, top_patterns, 0.0, {"label": "supportive"})

# --- Composite Analysis with Escalation Quiz ---
def analyze_composite(msg1, msg2, msg3, *answers_and_none):
    responses = answers_and_none[:len(ESCALATION_QUESTIONS)]
    none_selected = answers_and_none[-1]
    if none_selected:
        escalation_score = 0
    else:
        escalation_score = sum(w for (_, w), a in zip(ESCALATION_QUESTIONS, responses) if a)
    if escalation_score >= 16:
        escalation_level = "High"
    elif escalation_score >= 8:
        escalation_level = "Moderate"
    else:
        escalation_level = "Low"

    thresholds = THRESHOLDS.copy()
    messages = [msg1, msg2, msg3]
    active = [m for m in messages if m.strip()]
    if not active:
        return "Please enter at least one message."

    results = [analyze_single_message(m, thresholds, []) for m in active]
    abuse_scores = [r[0] for r in results]
    top_pattern = max({label for r in results for label in r[2]}, key=lambda l: abuse_scores[0])
    composite_abuse = round(sum(abuse_scores)/len(abuse_scores),2)

    out = f"Abuse Intensity: {composite_abuse}%\n"
    out += f"Escalation Potential: {escalation_level} ({escalation_score}/{sum(w for _,w in ESCALATION_QUESTIONS)})"
    out += generate_risk_snippet(composite_abuse, top_pattern)
    return out

textbox_inputs = [
    gr.Textbox(label="Message 1"),
    gr.Textbox(label="Message 2"),
    gr.Textbox(label="Message 3")
]

quiz_boxes = [gr.Checkbox(label=q) for q, _ in ESCALATION_QUESTIONS]
none_box = gr.Checkbox(label="None of the above")

iface = gr.Interface(
    fn=analyze_composite,
    inputs=textbox_inputs + quiz_boxes + [none_box],
    outputs=gr.Textbox(label="Results"),
    title="Abuse Pattern Detector + Escalation Quiz",
    allow_flagging="manual"
)

if __name__ == "__main__":
    iface.launch()