SamanthaStorm commited on
Commit
ae72bb4
·
verified ·
1 Parent(s): 37c4d94

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +431 -60
app.py CHANGED
@@ -1,74 +1,445 @@
1
  import gradio as gr
2
- import os
3
- import json
4
  import pandas as pd
5
- from datetime import datetime, timedelta
6
  import numpy as np
7
- from analyzer import TetherProAnalyzer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- # --- Sample data for testing ---
10
- def create_sample_data():
11
- sample = []
12
- now = datetime.now() - timedelta(days=30)
13
- for i in range(15):
14
- base = 25 + i*3.5 + (20 if i%5==0 else 0)
15
- abuse = min(95, max(5, base + np.random.normal(0,8)))
16
- sample.append({
17
- 'timestamp': (now + timedelta(days=i*2)).isoformat(),
18
- 'id': f'msg_{i:03d}',
19
- 'text': f"Sample message {i}",
20
- 'sender': 'A' if i%2==0 else 'B',
21
- 'abuse_score': round(abuse,1),
22
- 'darvo_score': round(min(1, max(0,0.2 + i*0.05)),3),
23
- 'boundary_health': 'unhealthy' if abuse>50 else 'healthy',
24
- 'patterns': ['control','gaslighting'] if abuse>60 else ['dismissiveness'],
25
- 'emotional_tone': 'neutral',
26
- 'risk_level': 'low'
27
- })
28
- return json.dumps(sample, indent=2)
 
 
 
 
 
 
 
 
 
29
 
30
- # --- CSV/XLS loader with timestamp conversion ---
31
- def load_messages_from_file(file_obj):
32
- ext = os.path.splitext(file_obj.name)[1].lower()
33
  try:
34
- if ext == '.csv':
35
- df = pd.read_csv(file_obj.name)
36
- elif ext in ('.xls','.xlsx'):
37
- df = pd.read_excel(file_obj.name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  else:
39
- return {'error': 'Upload a .csv or .xlsx'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  except Exception as e:
41
- return {'error': f'Failed to read file: {e}'}
 
42
 
43
- records = df.to_dict(orient='records')
44
- for r in records:
45
- for k,v in r.items():
46
- if hasattr(v, 'isoformat'):
47
- r[k] = v.isoformat()
48
- return json.dumps(records)
 
 
 
 
49
 
50
- def analyze_uploaded_file(file_obj):
51
- js = load_messages_from_file(file_obj)
52
- if isinstance(js, dict):
53
- return js
54
- return TetherProAnalyzer().analyze_conversation_history(js)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- # --- Gradio UI ---
57
- css = """
58
- .gradio-container { max-width: 1200px !important; margin: auto; }
59
- """
60
- with gr.Blocks(css=css) as demo:
61
- gr.Markdown("# Tether Pro Conversation Analyzer")
62
- with gr.Row():
63
- uploader = gr.File(label="Upload .csv or .xlsx", file_types=[".csv",".xlsx"])
64
- btn = gr.Button("Analyze")
65
- result = gr.JSON(label="Analysis Results")
66
- btn.click(analyze_uploaded_file, inputs=uploader, outputs=result)
 
 
 
 
 
 
 
 
 
 
 
67
 
68
- gr.Markdown("## Sample Data")
69
- sample_btn = gr.Button("Load Sample")
70
- sample_box = gr.Textbox(lines=10)
71
- sample_btn.click(create_sample_data, inputs=None, outputs=sample_box)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  if __name__ == "__main__":
74
- demo.launch()
 
 
1
  import gradio as gr
 
 
2
  import pandas as pd
 
3
  import numpy as np
4
+ import os
5
+ import json
6
+ import logging
7
+ import traceback
8
+ from datetime import datetime
9
+ import plotly.graph_objects as go
10
+ import plotly.express as px
11
+ from models import ModelManager
12
+ from analyzer import MessageAnalyzer
13
+ from utils import (
14
+ parse_chat_data, generate_timeline_chart, generate_pattern_frequency_chart,
15
+ generate_sender_comparison_chart, generate_time_of_day_heatmap
16
+ )
17
+
18
+ # Set up logging
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Initialize models
23
+ model_manager = ModelManager()
24
+ model_manager.load_models()
25
+
26
+ # Initialize analyzer
27
+ analyzer = MessageAnalyzer(model_manager)
28
+
29
+ def analyze_file(file_path):
30
+ """Analyze uploaded chat file"""
31
+ try:
32
+ # Parse chat data
33
+ df = parse_chat_data(file_path)
34
+
35
+ # Analyze chat history
36
+ results_df, summary = analyzer.analyze_chat_history(df)
37
+
38
+ return results_df, summary
39
+ except Exception as e:
40
+ logger.error(f"Error analyzing file: {e}")
41
+ logger.error(traceback.format_exc())
42
+ return None, {"error": str(e)}
43
 
44
+ def analyze_single_message(message):
45
+ """Analyze a single message"""
46
+ try:
47
+ if not message.strip():
48
+ return {
49
+ "abuse_score": 0,
50
+ "detected_patterns": [],
51
+ "sentiment": "neutral",
52
+ "emotional_tone": "neutral",
53
+ "boundary_assessment": {"assessment": "neutral"},
54
+ "risk_level": "Low"
55
+ }
56
+
57
+ # Analyze message
58
+ analysis = analyzer.analyze_message(message)
59
+
60
+ return analysis
61
+ except Exception as e:
62
+ logger.error(f"Error analyzing message: {e}")
63
+ logger.error(traceback.format_exc())
64
+ return {
65
+ "error": str(e),
66
+ "abuse_score": 0,
67
+ "detected_patterns": [],
68
+ "sentiment": "error",
69
+ "emotional_tone": "error",
70
+ "boundary_assessment": {"assessment": "error"},
71
+ "risk_level": "Unknown"
72
+ }
73
 
74
+ def format_analysis_results(analysis):
75
+ """Format analysis results for display"""
 
76
  try:
77
+ # Format abuse score
78
+ abuse_score_html = f"""
79
+ <div style="margin-bottom: 20px;">
80
+ <h3>Abuse Score: {analysis['abuse_score']:.1f}%</h3>
81
+ <div style="background: #f0f0f0; height: 20px; width: 100%; border-radius: 10px;">
82
+ <div style="background: {get_risk_color(analysis['risk_level'])};
83
+ height: 100%;
84
+ width: {min(100, max(0, analysis['abuse_score']))}%;
85
+ border-radius: 10px;">
86
+ </div>
87
+ </div>
88
+ <p>Risk Level: <strong>{analysis['risk_level']}</strong></p>
89
+ </div>
90
+ """
91
+
92
+ # Format detected patterns
93
+ patterns_html = "<h3>Detected Patterns</h3>"
94
+ if analysis['detected_patterns']:
95
+ patterns_html += "<ul>"
96
+ for pattern in analysis['detected_patterns']:
97
+ patterns_html += f"<li>{pattern}</li>"
98
+ patterns_html += "</ul>"
99
  else:
100
+ patterns_html += "<p>No concerning patterns detected.</p>"
101
+
102
+ # Format sentiment and emotional tone
103
+ sentiment_html = f"""
104
+ <h3>Communication Analysis</h3>
105
+ <p><strong>Sentiment:</strong> {analysis['sentiment']}</p>
106
+ <p><strong>Emotional Tone:</strong> {analysis['emotional_tone']}</p>
107
+ <p><strong>DARVO Score:</strong> {analysis['darvo_score']:.3f}</p>
108
+ """
109
+
110
+ # Format boundary assessment
111
+ boundary_html = f"""
112
+ <h3>Boundary Health</h3>
113
+ <p><strong>Assessment:</strong> {analysis['boundary_assessment']['assessment']}</p>
114
+ <p><strong>Description:</strong> {analysis['boundary_assessment']['description']}</p>
115
+ """
116
+
117
+ # Combine all sections
118
+ html = f"""
119
+ <div style="padding: 20px; border: 1px solid #ddd; border-radius: 10px;">
120
+ {abuse_score_html}
121
+ {patterns_html}
122
+ {sentiment_html}
123
+ {boundary_html}
124
+ </div>
125
+ """
126
+
127
+ return html
128
  except Exception as e:
129
+ logger.error(f"Error formatting analysis results: {e}")
130
+ return f"<p>Error formatting results: {str(e)}</p>"
131
 
132
+ def get_risk_color(risk_level):
133
+ """Get color for risk level"""
134
+ colors = {
135
+ "Critical": "#ef4444",
136
+ "High": "#f97316",
137
+ "Moderate": "#f59e0b",
138
+ "Low": "#10b981",
139
+ "Unknown": "#6b7280"
140
+ }
141
+ return colors.get(risk_level, "#6b7280")
142
 
143
+ def format_summary_results(summary):
144
+ """Format summary results for display"""
145
+ try:
146
+ # Format basic info
147
+ basic_info = f"""
148
+ <h2>Chat Analysis Summary</h2>
149
+ <p><strong>Messages Analyzed:</strong> {summary['message_count']}</p>
150
+ <p><strong>Date Range:</strong> {summary['date_range']['start']} to {summary['date_range']['end']}</p>
151
+ <p><strong>Overall Risk Level:</strong> <span style="color: {get_risk_color(summary['overall_risk_level'])};
152
+ font-weight: bold;">
153
+ {summary['overall_risk_level']}</span></p>
154
+ """
155
+
156
+ # Format sender stats
157
+ sender_stats = "<h3>Sender Analysis</h3>"
158
+ if summary['sender_stats']:
159
+ sender_stats += "<table style='width: 100%; border-collapse: collapse;'>"
160
+ sender_stats += "<tr><th style='text-align: left; padding: 8px; border-bottom: 1px solid #ddd;'>Sender</th>"
161
+ sender_stats += "<th style='text-align: left; padding: 8px; border-bottom: 1px solid #ddd;'>Messages</th>"
162
+ sender_stats += "<th style='text-align: left; padding: 8px; border-bottom: 1px solid #ddd;'>Avg. Abuse Score</th>"
163
+ sender_stats += "<th style='text-align: left; padding: 8px; border-bottom: 1px solid #ddd;'>Common Patterns</th></tr>"
164
+
165
+ for sender, stats in summary['sender_stats'].items():
166
+ common_patterns = ", ".join([p[0] for p in stats['common_patterns'][:2]]) if stats['common_patterns'] else "None"
167
+ sender_stats += f"<tr><td style='padding: 8px; border-bottom: 1px solid #ddd;'>{sender}</td>"
168
+ sender_stats += f"<td style='padding: 8px; border-bottom: 1px solid #ddd;'>{stats['message_count']}</td>"
169
+ sender_stats += f"<td style='padding: 8px; border-bottom: 1px solid #ddd;'>{stats['avg_abuse_score']:.1f}%</td>"
170
+ sender_stats += f"<td style='padding: 8px; border-bottom: 1px solid #ddd;'>{common_patterns}</td></tr>"
171
+
172
+ sender_stats += "</table>"
173
+ else:
174
+ sender_stats += "<p>No sender statistics available.</p>"
175
+
176
+ # Format primary abuser
177
+ primary_abuser = "<h3>Primary Concern</h3>"
178
+ if summary['primary_abuser']:
179
+ primary_abuser += f"<p>The analysis indicates that <strong>{summary['primary_abuser']}</strong> "
180
+ primary_abuser += "shows the highest percentage of concerning communication patterns.</p>"
181
+ else:
182
+ primary_abuser += "<p>No clear pattern of concerning communication from a specific sender.</p>"
183
+
184
+ # Format escalation data
185
+ escalation = "<h3>Escalation Analysis</h3>"
186
+ if summary['escalation_data']:
187
+ trend = summary['escalation_data'].get('trend_direction', 'unknown')
188
+ trend_strength = summary['escalation_data'].get('trend_strength', 0)
189
+ recent_avg = summary['escalation_data'].get('recent_average', 0)
190
+ future_prediction = summary['escalation_data'].get('future_prediction')
191
+ cyclic_pattern = summary['escalation_data'].get('cyclic_pattern', False)
192
+
193
+ escalation += f"<p><strong>Trend Direction:</strong> {trend.title()}</p>"
194
+ escalation += f"<p><strong>Trend Strength:</strong> {trend_strength:.2f}</p>"
195
+ escalation += f"<p><strong>Recent Average:</strong> {recent_avg:.1f}%</p>"
196
+
197
+ if future_prediction is not None:
198
+ escalation += f"<p><strong>7-Day Prediction:</strong> {future_prediction:.1f}%</p>"
199
+
200
+ if cyclic_pattern:
201
+ cycle_period = summary['escalation_data'].get('cycle_period', 'unknown')
202
+ escalation += f"<p><strong>Cyclic Pattern Detected:</strong> Yes (approximately {cycle_period} days)</p>"
203
+ else:
204
+ escalation += "<p>No escalation data available.</p>"
205
+
206
+ # Format recommendations
207
+ recommendations = "<h3>Professional Recommendations</h3>"
208
+ if summary['recommendations']:
209
+ recommendations += "<div style='max-height: 300px; overflow-y: auto;'>"
210
+ for rec in summary['recommendations']:
211
+ recommendations += f"<div style='margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px;'>"
212
+ recommendations += f"<h4>{rec['title']}</h4>"
213
+ recommendations += f"<p>{rec['description']}</p>"
214
+
215
+ if rec['actions']:
216
+ recommendations += "<ul>"
217
+ for action in rec['actions']:
218
+ recommendations += f"<li>{action}</li>"
219
+ recommendations += "</ul>"
220
+
221
+ recommendations += "</div>"
222
+ recommendations += "</div>"
223
+ else:
224
+ recommendations += "<p>No recommendations available.</p>"
225
+
226
+ # Combine all sections
227
+ html = f"""
228
+ <div style="padding: 20px; border: 1px solid #ddd; border-radius: 10px;">
229
+ {basic_info}
230
+ {sender_stats}
231
+ {primary_abuser}
232
+ {escalation}
233
+ {recommendations}
234
+ </div>
235
+ """
236
+
237
+ return html
238
+ except Exception as e:
239
+ logger.error(f"Error formatting summary results: {e}")
240
+ return f"<p>Error formatting summary: {str(e)}</p>"
241
 
242
+ def format_safety_plan(safety_plan):
243
+ """Format safety plan for display"""
244
+ try:
245
+ # Convert markdown-like formatting to HTML
246
+ html = safety_plan.replace("**", "<strong>").replace("**", "</strong>")
247
+ html = html.replace("\n\n", "<br><br>")
248
+ html = html.replace("\n•", "<br>•")
249
+
250
+ # Wrap in styled div
251
+ html = f"""
252
+ <div style="padding: 20px; border: 1px solid #ddd; border-radius: 10px; background-color: #f8f9fa;">
253
+ <h2>Safety Plan</h2>
254
+ <div style="white-space: pre-wrap;">
255
+ {html}
256
+ </div>
257
+ </div>
258
+ """
259
+
260
+ return html
261
+ except Exception as e:
262
+ logger.error(f"Error formatting safety plan: {e}")
263
+ return f"<p>Error formatting safety plan: {str(e)}</p>"
264
 
265
+ def create_interface():
266
+ """Create Gradio interface"""
267
+ with gr.Blocks(title="Relationship Pattern Analyzer Pro") as demo:
268
+ gr.HTML("""
269
+ <div style="text-align: center; padding: 20px;">
270
+ <h1>Relationship Pattern Analyzer Pro</h1>
271
+ <p>Upload a chat history file to analyze communication patterns and identify concerning behaviors.</p>
272
+ </div>
273
+ """)
274
+
275
+ with gr.Tab("Chat Analysis"):
276
+ with gr.Row():
277
+ with gr.Column(scale=1):
278
+ file_input = gr.File(
279
+ label="Upload Chat File (CSV, XLSX, or JSON)",
280
+ file_types=["csv", "xlsx", "json"]
281
+ )
282
+ analyze_button = gr.Button("Analyze Chat History")
283
+
284
+ with gr.Column(scale=2):
285
+ summary_output = gr.HTML(
286
+ label="Analysis Summary",
287
+ value="<p>Upload a file and click 'Analyze Chat History' to see results.</p>"
288
+ )
289
+
290
+ with gr.Row():
291
+ with gr.Column():
292
+ timeline_plot = gr.Plot(label="Abuse Score Timeline")
293
+
294
+ with gr.Column():
295
+ pattern_plot = gr.Plot(label="Pattern Frequency")
296
+
297
+ with gr.Row():
298
+ with gr.Column():
299
+ sender_plot = gr.Plot(label="Sender Comparison")
300
+
301
+ with gr.Column():
302
+ heatmap_plot = gr.Plot(label="Time of Day Analysis")
303
+
304
+ with gr.Row():
305
+ safety_plan_output = gr.HTML(
306
+ label="Safety Plan",
307
+ value="<p>Safety plan will appear here after analysis.</p>"
308
+ )
309
+
310
+ with gr.Tab("Single Message Analysis"):
311
+ with gr.Row():
312
+ with gr.Column(scale=1):
313
+ message_input = gr.Textbox(
314
+ label="Enter Message",
315
+ placeholder="Type or paste a message to analyze...",
316
+ lines=5
317
+ )
318
+ analyze_message_button = gr.Button("Analyze Message")
319
+
320
+ with gr.Column(scale=2):
321
+ message_analysis_output = gr.HTML(
322
+ label="Message Analysis",
323
+ value="<p>Enter a message and click 'Analyze Message' to see results.</p>"
324
+ )
325
+
326
+ with gr.Tab("About"):
327
+ gr.HTML("""
328
+ <div style="padding: 20px;">
329
+ <h2>About Relationship Pattern Analyzer Pro</h2>
330
+ <p>This tool helps identify potentially concerning communication patterns in relationships.</p>
331
+
332
+ <h3>Features</h3>
333
+ <ul>
334
+ <li><strong>Chat Analysis:</strong> Upload an entire chat history to analyze communication patterns over time</li>
335
+ <li><strong>Temporal Analysis:</strong> Detect escalation patterns, cycles of abuse, and time-based trends</li>
336
+ <li><strong>Boundary Health:</strong> Assess whether messages demonstrate healthy or unhealthy relational boundaries</li>
337
+ <li><strong>Intent Analysis:</strong> Identify manipulative tactics and emotional tone</li>
338
+ <li><strong>Safety Planning:</strong> Get personalized safety recommendations based on detected patterns</li>
339
+ <li><strong>Professional Insights:</strong> View recommendations for professional intervention</li>
340
+ </ul>
341
+
342
+ <h3>Privacy Notice</h3>
343
+ <p>Your data is processed locally and is not stored or shared. This tool is for educational purposes only and not a substitute for professional counseling or legal advice.</p>
344
+
345
+ <h3>Emergency Resources</h3>
346
+ <p><strong>National Domestic Violence Hotline:</strong> 1-800-799-7233</p>
347
+ <p><strong>Crisis Text Line:</strong> Text START to 88788</p>
348
+ <p><strong>Emergency:</strong> Call 911</p>
349
+ </div>
350
+ """)
351
+
352
+ # Define event handlers
353
+ def handle_file_analysis(file_path):
354
+ if not file_path:
355
+ return (
356
+ "<p>Please upload a file first.</p>",
357
+ None, None, None, None,
358
+ "<p>Please upload a file first.</p>"
359
+ )
360
+
361
+ try:
362
+ # Analyze file
363
+ results_df, summary = analyze_file(file_path)
364
+
365
+ if "error" in summary:
366
+ return (
367
+ f"<p>Error: {summary['error']}</p>",
368
+ None, None, None, None,
369
+ "<p>Error analyzing file.</p>"
370
+ )
371
+
372
+ # Format summary
373
+ summary_html = format_summary_results(summary)
374
+
375
+ # Generate plots
376
+ timeline = generate_timeline_chart(results_df)
377
+ pattern_freq = generate_pattern_frequency_chart(results_df)
378
+ sender_comp = generate_sender_comparison_chart(results_df)
379
+ time_heatmap = generate_time_of_day_heatmap(results_df)
380
+
381
+ # Format safety plan
382
+ safety_plan_html = format_safety_plan(summary['safety_plan'])
383
+
384
+ return (
385
+ summary_html,
386
+ timeline,
387
+ pattern_freq,
388
+ sender_comp,
389
+ time_heatmap,
390
+ safety_plan_html
391
+ )
392
+ except Exception as e:
393
+ logger.error(f"Error in handle_file_analysis: {e}")
394
+ logger.error(traceback.format_exc())
395
+ return (
396
+ f"<p>Error analyzing file: {str(e)}</p>",
397
+ None, None, None, None,
398
+ "<p>Error analyzing file.</p>"
399
+ )
400
+
401
+ def handle_message_analysis(message):
402
+ if not message.strip():
403
+ return "<p>Please enter a message first.</p>"
404
+
405
+ try:
406
+ # Analyze message
407
+ analysis = analyze_single_message(message)
408
+
409
+ if "error" in analysis:
410
+ return f"<p>Error: {analysis['error']}</p>"
411
+
412
+ # Format analysis
413
+ html = format_analysis_results(analysis)
414
+
415
+ return html
416
+ except Exception as e:
417
+ logger.error(f"Error in handle_message_analysis: {e}")
418
+ logger.error(traceback.format_exc())
419
+ return f"<p>Error analyzing message: {str(e)}</p>"
420
+
421
+ # Connect event handlers
422
+ analyze_button.click(
423
+ handle_file_analysis,
424
+ inputs=[file_input],
425
+ outputs=[
426
+ summary_output,
427
+ timeline_plot,
428
+ pattern_plot,
429
+ sender_plot,
430
+ heatmap_plot,
431
+ safety_plan_output
432
+ ]
433
+ )
434
+
435
+ analyze_message_button.click(
436
+ handle_message_analysis,
437
+ inputs=[message_input],
438
+ outputs=[message_analysis_output]
439
+ )
440
+
441
+ return demo
442
 
443
  if __name__ == "__main__":
444
+ demo = create_interface()
445
+ demo.launch(server_name="0.0.0.0", server_port=7860)