Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,433 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import torch
|
3 |
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
|
4 |
+
import numpy as np
|
5 |
+
import json
|
6 |
+
from datetime import datetime
|
7 |
+
import logging
|
8 |
+
|
9 |
+
# Set up logging
|
10 |
+
logging.basicConfig(level=logging.INFO)
|
11 |
+
logger = logging.getLogger(__name__)
|
12 |
+
|
13 |
+
class DistilBERTHateSpeechDetector:
|
14 |
+
def __init__(self):
|
15 |
+
self.model = None
|
16 |
+
self.tokenizer = None
|
17 |
+
self.classifier = None
|
18 |
+
self.load_model()
|
19 |
+
|
20 |
+
def load_model(self):
|
21 |
+
"""Load the fine-tuned DistilBERT model"""
|
22 |
+
try:
|
23 |
+
logger.info("Loading DistilBERT hate speech detection model...")
|
24 |
+
|
25 |
+
# Try to load from local model directory
|
26 |
+
model_path = "./model"
|
27 |
+
|
28 |
+
# Load tokenizer
|
29 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_path)
|
30 |
+
logger.info("✅ Tokenizer loaded successfully")
|
31 |
+
|
32 |
+
# Load model
|
33 |
+
self.model = AutoModelForSequenceClassification.from_pretrained(
|
34 |
+
model_path,
|
35 |
+
torch_dtype=torch.float32,
|
36 |
+
device_map="auto"
|
37 |
+
)
|
38 |
+
logger.info("✅ DistilBERT model loaded successfully")
|
39 |
+
|
40 |
+
# Create pipeline
|
41 |
+
self.classifier = pipeline(
|
42 |
+
"text-classification",
|
43 |
+
model=self.model,
|
44 |
+
tokenizer=self.tokenizer,
|
45 |
+
return_all_scores=True,
|
46 |
+
device=0 if torch.cuda.is_available() else -1
|
47 |
+
)
|
48 |
+
|
49 |
+
# Get model info
|
50 |
+
logger.info(f"Model architecture: {self.model.config.architectures[0]}")
|
51 |
+
logger.info(f"Number of labels: {self.model.config.num_labels}")
|
52 |
+
logger.info(f"Max sequence length: {self.model.config.max_position_embeddings}")
|
53 |
+
|
54 |
+
except Exception as e:
|
55 |
+
logger.error(f"❌ Error loading custom model: {e}")
|
56 |
+
logger.info("🔄 Falling back to public model...")
|
57 |
+
|
58 |
+
# Fallback to a public model
|
59 |
+
try:
|
60 |
+
self.classifier = pipeline(
|
61 |
+
"text-classification",
|
62 |
+
model="martin-ha/toxic-comment-model",
|
63 |
+
return_all_scores=True
|
64 |
+
)
|
65 |
+
logger.info("✅ Fallback model loaded")
|
66 |
+
except Exception as fallback_error:
|
67 |
+
logger.error(f"❌ Fallback model also failed: {fallback_error}")
|
68 |
+
raise Exception("Failed to load any model")
|
69 |
+
|
70 |
+
def preprocess_text(self, text):
|
71 |
+
"""Preprocess text for better analysis"""
|
72 |
+
if not text or not text.strip():
|
73 |
+
return ""
|
74 |
+
|
75 |
+
# Basic preprocessing
|
76 |
+
text = text.strip()
|
77 |
+
# Remove excessive whitespace
|
78 |
+
text = " ".join(text.split())
|
79 |
+
|
80 |
+
return text
|
81 |
+
|
82 |
+
def detect_hate_speech(self, text):
|
83 |
+
"""Detect hate speech with detailed analysis"""
|
84 |
+
if not text or not text.strip():
|
85 |
+
return {
|
86 |
+
"status": "❌ Please enter some text to analyze.",
|
87 |
+
"prediction": "No input",
|
88 |
+
"confidence": 0.0,
|
89 |
+
"all_scores": {},
|
90 |
+
"risk_level": "Unknown"
|
91 |
+
}
|
92 |
+
|
93 |
+
try:
|
94 |
+
# Preprocess text
|
95 |
+
processed_text = self.preprocess_text(text)
|
96 |
+
|
97 |
+
# Get predictions
|
98 |
+
results = self.classifier(processed_text)
|
99 |
+
|
100 |
+
# Handle different output formats
|
101 |
+
if isinstance(results, list) and len(results) > 0:
|
102 |
+
if isinstance(results[0], list):
|
103 |
+
results = results[0]
|
104 |
+
|
105 |
+
# Parse results
|
106 |
+
all_scores = {}
|
107 |
+
max_score = 0
|
108 |
+
predicted_label = "UNKNOWN"
|
109 |
+
|
110 |
+
for result in results:
|
111 |
+
label = result["label"]
|
112 |
+
score = result["score"]
|
113 |
+
all_scores[label] = {
|
114 |
+
"score": score,
|
115 |
+
"percentage": f"{score*100:.2f}%",
|
116 |
+
"confidence": f"{score:.4f}"
|
117 |
+
}
|
118 |
+
|
119 |
+
if score > max_score:
|
120 |
+
max_score = score
|
121 |
+
predicted_label = label
|
122 |
+
|
123 |
+
# Determine hate speech status
|
124 |
+
hate_keywords = ["HATE", "TOXIC", "NEGATIVE", "HARMFUL", "1", "LABEL_1"]
|
125 |
+
clean_keywords = ["CLEAN", "NORMAL", "POSITIVE", "SAFE", "0", "LABEL_0"]
|
126 |
+
|
127 |
+
is_hate_speech = False
|
128 |
+
risk_level = "Low"
|
129 |
+
|
130 |
+
if any(keyword in predicted_label.upper() for keyword in hate_keywords):
|
131 |
+
if max_score > 0.8:
|
132 |
+
is_hate_speech = True
|
133 |
+
risk_level = "High"
|
134 |
+
status = f"🚨 High confidence hate speech detected! ({predicted_label}: {max_score:.2%})"
|
135 |
+
elif max_score > 0.6:
|
136 |
+
is_hate_speech = True
|
137 |
+
risk_level = "Medium"
|
138 |
+
status = f"⚠️ Potential hate speech detected ({predicted_label}: {max_score:.2%})"
|
139 |
+
else:
|
140 |
+
risk_level = "Low-Medium"
|
141 |
+
status = f"⚡ Low confidence detection ({predicted_label}: {max_score:.2%})"
|
142 |
+
else:
|
143 |
+
risk_level = "Low"
|
144 |
+
status = f"✅ No hate speech detected ({predicted_label}: {max_score:.2%})"
|
145 |
+
|
146 |
+
return {
|
147 |
+
"status": status,
|
148 |
+
"prediction": predicted_label,
|
149 |
+
"confidence": max_score,
|
150 |
+
"all_scores": all_scores,
|
151 |
+
"risk_level": risk_level,
|
152 |
+
"is_hate_speech": is_hate_speech
|
153 |
+
}
|
154 |
+
|
155 |
+
except Exception as e:
|
156 |
+
logger.error(f"Analysis error: {e}")
|
157 |
+
return {
|
158 |
+
"status": f"❌ Error during analysis: {str(e)}",
|
159 |
+
"prediction": "Error",
|
160 |
+
"confidence": 0.0,
|
161 |
+
"all_scores": {},
|
162 |
+
"risk_level": "Unknown"
|
163 |
+
}
|
164 |
+
|
165 |
+
def generate_counter_narrative(self, text, detection_result):
|
166 |
+
"""Generate educational counter-narrative based on detection"""
|
167 |
+
if not detection_result.get("is_hate_speech", False):
|
168 |
+
return "✨ Great! This text promotes positive communication. Keep up the constructive dialogue!"
|
169 |
+
|
170 |
+
# Counter-narratives based on risk level
|
171 |
+
risk_level = detection_result.get("risk_level", "Low")
|
172 |
+
|
173 |
+
high_risk_responses = [
|
174 |
+
"🛡️ **Educational Response**: This type of language can cause real harm to individuals and communities. Consider how your words might affect others and try rephrasing with respect and empathy.",
|
175 |
+
"💡 **Constructive Alternative**: Instead of using harmful language, try expressing your concerns in a way that opens dialogue rather than shutting it down. Every person deserves dignity and respect.",
|
176 |
+
"🌍 **Community Impact**: Hate speech can escalate tensions and divide communities. Consider how you can contribute to a more inclusive and understanding environment.",
|
177 |
+
"📚 **Learning Opportunity**: Research shows that exposure to diverse perspectives actually strengthens critical thinking. Consider engaging with different viewpoints constructively."
|
178 |
+
]
|
179 |
+
|
180 |
+
medium_risk_responses = [
|
181 |
+
"🤔 **Reflection Point**: This language might be interpreted as harmful by some. Consider rewording to express your point more constructively.",
|
182 |
+
"💬 **Communication Tip**: Try framing your message in a way that invites discussion rather than potentially excluding or hurting others.",
|
183 |
+
"🎯 **Focus Shift**: Instead of focusing on differences that divide, consider highlighting shared values or common ground.",
|
184 |
+
"🔄 **Reframe Opportunity**: How might you express this same sentiment in a way that brings people together rather than apart?"
|
185 |
+
]
|
186 |
+
|
187 |
+
if risk_level == "High":
|
188 |
+
responses = high_risk_responses
|
189 |
+
elif risk_level == "Medium":
|
190 |
+
responses = medium_risk_responses
|
191 |
+
else:
|
192 |
+
responses = [
|
193 |
+
"💭 **Gentle Reminder**: While this might not be clearly harmful, consider how your words might be received by others.",
|
194 |
+
"🌱 **Growth Mindset**: Every interaction is an opportunity to build understanding and connection.",
|
195 |
+
"🤝 **Bridge Building**: Consider how you can use your voice to bring people together rather than create distance."
|
196 |
+
]
|
197 |
+
|
198 |
+
import random
|
199 |
+
return random.choice(responses)
|
200 |
+
|
201 |
+
def get_model_info(self):
|
202 |
+
"""Get information about the loaded model"""
|
203 |
+
if self.model:
|
204 |
+
return {
|
205 |
+
"Model Type": "DistilBERT (Fine-tuned)",
|
206 |
+
"Architecture": self.model.config.architectures[0],
|
207 |
+
"Parameters": f"~{66}M parameters",
|
208 |
+
"Max Length": self.model.config.max_position_embeddings,
|
209 |
+
"Labels": self.model.config.num_labels,
|
210 |
+
"Framework": "PyTorch + Transformers"
|
211 |
+
}
|
212 |
+
return {"Model": "Fallback model in use"}
|
213 |
+
|
214 |
+
# Initialize the detector
|
215 |
+
logger.info("Initializing DistilBERT Hate Speech Detector...")
|
216 |
+
detector = DistilBERTHateSpeechDetector()
|
217 |
+
|
218 |
+
def analyze_text(text):
|
219 |
+
"""Main analysis function for Gradio interface"""
|
220 |
+
start_time = datetime.now()
|
221 |
+
|
222 |
+
# Perform detection
|
223 |
+
detection_result = detector.detect_hate_speech(text)
|
224 |
+
|
225 |
+
# Generate counter-narrative
|
226 |
+
counter_narrative = detector.generate_counter_narrative(text, detection_result)
|
227 |
+
|
228 |
+
# Calculate processing time
|
229 |
+
processing_time = (datetime.now() - start_time).total_seconds()
|
230 |
+
|
231 |
+
# Format results for display
|
232 |
+
status = detection_result["status"]
|
233 |
+
all_scores = detection_result["all_scores"]
|
234 |
+
|
235 |
+
# Add processing info
|
236 |
+
info_text = f"⏱️ Processed in {processing_time:.3f} seconds | Risk Level: {detection_result['risk_level']}"
|
237 |
+
|
238 |
+
return status, all_scores, counter_narrative, info_text
|
239 |
+
|
240 |
+
def get_model_details():
|
241 |
+
"""Get model information for display"""
|
242 |
+
return detector.get_model_info()
|
243 |
+
|
244 |
+
# Create the Gradio interface
|
245 |
+
with gr.Blocks(
|
246 |
+
title="DistilBERT Hate Speech Detection & Counter-Narrative Generator",
|
247 |
+
theme=gr.themes.Soft(),
|
248 |
+
css="""
|
249 |
+
.gradio-container {
|
250 |
+
max-width: 1400px !important;
|
251 |
+
}
|
252 |
+
.status-box {
|
253 |
+
padding: 1rem;
|
254 |
+
border-radius: 8px;
|
255 |
+
margin: 0.5rem 0;
|
256 |
+
}
|
257 |
+
"""
|
258 |
+
) as demo:
|
259 |
+
|
260 |
+
gr.Markdown("""
|
261 |
+
# 🛡️ DistilBERT Hate Speech Detection & Counter-Narrative Generator
|
262 |
+
|
263 |
+
**Advanced AI Agent System for Content Moderation & Education**
|
264 |
+
|
265 |
+
🤖 **Powered by Fine-tuned DistilBERT** - Efficient and accurate hate speech detection
|
266 |
+
📚 **Educational Counter-Narratives** - AI-generated constructive responses
|
267 |
+
⚡ **Real-time Processing** - Fast analysis with detailed confidence scores
|
268 |
+
🎯 **Multi-level Risk Assessment** - Nuanced understanding of content severity
|
269 |
+
""")
|
270 |
+
|
271 |
+
with gr.Tab("🔍 Text Analysis"):
|
272 |
+
with gr.Row():
|
273 |
+
with gr.Column(scale=2):
|
274 |
+
text_input = gr.Textbox(
|
275 |
+
label="Enter text to analyze",
|
276 |
+
placeholder="Type or paste text here for hate speech analysis...",
|
277 |
+
lines=5,
|
278 |
+
max_lines=15
|
279 |
+
)
|
280 |
+
|
281 |
+
with gr.Row():
|
282 |
+
analyze_btn = gr.Button("🔍 Analyze Text", variant="primary", size="lg")
|
283 |
+
clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
|
284 |
+
|
285 |
+
gr.Examples(
|
286 |
+
examples=[
|
287 |
+
["This is a wonderful day to learn something new!"],
|
288 |
+
["I respectfully disagree with that policy, but I understand your perspective."],
|
289 |
+
["The team did an excellent job on this project. Well done everyone!"],
|
290 |
+
["Thank you for sharing your thoughts. Let's discuss this constructively."],
|
291 |
+
["That restaurant has amazing food and great service!"],
|
292 |
+
["I appreciate you taking the time to explain your viewpoint."]
|
293 |
+
],
|
294 |
+
inputs=text_input,
|
295 |
+
label="📝 Try these positive examples:"
|
296 |
+
)
|
297 |
+
|
298 |
+
with gr.Row():
|
299 |
+
with gr.Column(scale=1):
|
300 |
+
status_output = gr.Textbox(
|
301 |
+
label="🎯 Detection Result",
|
302 |
+
interactive=False,
|
303 |
+
lines=3
|
304 |
+
)
|
305 |
+
|
306 |
+
processing_info = gr.Textbox(
|
307 |
+
label="ℹ️ Processing Info",
|
308 |
+
interactive=False,
|
309 |
+
lines=1
|
310 |
+
)
|
311 |
+
|
312 |
+
with gr.Column(scale=1):
|
313 |
+
counter_narrative_output = gr.Textbox(
|
314 |
+
label="💡 Educational Counter-Narrative",
|
315 |
+
interactive=False,
|
316 |
+
lines=4
|
317 |
+
)
|
318 |
+
|
319 |
+
with gr.Row():
|
320 |
+
scores_output = gr.JSON(
|
321 |
+
label="📊 Detailed Confidence Scores",
|
322 |
+
visible=True
|
323 |
+
)
|
324 |
+
|
325 |
+
with gr.Tab("🔧 Model Information"):
|
326 |
+
with gr.Row():
|
327 |
+
with gr.Column():
|
328 |
+
gr.Markdown("## 🤖 Model Details")
|
329 |
+
model_info = gr.JSON(
|
330 |
+
label="Model Configuration",
|
331 |
+
value=get_model_details()
|
332 |
+
)
|
333 |
+
|
334 |
+
with gr.Column():
|
335 |
+
gr.Markdown("""
|
336 |
+
## 📈 Performance Characteristics
|
337 |
+
|
338 |
+
**DistilBERT Advantages:**
|
339 |
+
- ⚡ **Fast Processing**: 60% smaller than BERT
|
340 |
+
- 🎯 **High Accuracy**: Retains 97% of BERT's performance
|
341 |
+
- 💾 **Memory Efficient**: Lower computational requirements
|
342 |
+
- 🔄 **Real-time Ready**: Suitable for production deployment
|
343 |
+
|
344 |
+
**Fine-tuning Benefits:**
|
345 |
+
- 🎯 **Domain-Specific**: Trained on hate speech datasets
|
346 |
+
- 📊 **Balanced Performance**: Optimized precision-recall balance
|
347 |
+
- 🔍 **Context-Aware**: Understanding of nuanced language patterns
|
348 |
+
""")
|
349 |
+
|
350 |
+
with gr.Tab("📋 About & Usage"):
|
351 |
+
gr.Markdown("""
|
352 |
+
## 🎯 System Overview
|
353 |
+
|
354 |
+
This demonstration showcases an advanced AI agent system combining:
|
355 |
+
|
356 |
+
### 🤖 AI Agent Architecture
|
357 |
+
1. **Detection Agent**: Fine-tuned DistilBERT classifier
|
358 |
+
2. **Analysis Agent**: Risk assessment and confidence scoring
|
359 |
+
3. **Counter-Narrative Agent**: Educational response generation
|
360 |
+
4. **Monitoring Agent**: Performance tracking and logging
|
361 |
+
|
362 |
+
### 🛡️ Content Moderation Pipeline
|
363 |
+
```
|
364 |
+
Input Text → Preprocessing → DistilBERT Analysis → Risk Assessment → Counter-Narrative Generation → Results
|
365 |
+
```
|
366 |
+
|
367 |
+
### 📊 Risk Level Classification
|
368 |
+
- **🚨 High Risk (>80% confidence)**: Clear hate speech detection
|
369 |
+
- **⚠️ Medium Risk (60-80%)**: Potential harmful content
|
370 |
+
- **⚡ Low-Medium Risk (40-60%)**: Uncertain classification
|
371 |
+
- **✅ Low Risk (<40%)**: Safe content
|
372 |
+
|
373 |
+
## 🔧 Technical Implementation
|
374 |
+
|
375 |
+
**Model Architecture:**
|
376 |
+
- Base: DistilBERT (distilbert-base-uncased)
|
377 |
+
- Task: Sequence Classification
|
378 |
+
- Parameters: ~66M (vs 110M for BERT-base)
|
379 |
+
- Max Sequence Length: 512 tokens
|
380 |
+
|
381 |
+
**Key Features:**
|
382 |
+
- Real-time inference with <1 second response time
|
383 |
+
- Confidence-based risk assessment
|
384 |
+
- Educational counter-narrative generation
|
385 |
+
- Comprehensive error handling and fallbacks
|
386 |
+
|
387 |
+
## ⚠️ Important Disclaimers
|
388 |
+
|
389 |
+
- 🔬 **Research Demonstration**: Not ready for production without additional safeguards
|
390 |
+
- 👥 **Human Oversight Required**: AI should supplement, not replace human moderation
|
391 |
+
- ⚖️ **Bias Awareness**: Models can reflect biases present in training data
|
392 |
+
- 🔒 **Privacy Conscious**: No data is stored or logged from this demo
|
393 |
+
- 🌍 **Context Matters**: Cultural and contextual factors affect interpretation
|
394 |
+
|
395 |
+
## 🚀 Potential Applications
|
396 |
+
|
397 |
+
- **Social Media Platforms**: Automated content moderation
|
398 |
+
- **Educational Tools**: Teaching about respectful communication
|
399 |
+
- **Community Forums**: Maintaining healthy discussions
|
400 |
+
- **Content Creation**: Writing assistance for inclusive language
|
401 |
+
- **Research**: Studying patterns in online discourse
|
402 |
+
|
403 |
+
## 📞 Feedback & Development
|
404 |
+
|
405 |
+
This demo represents the cutting edge of AI-powered content moderation.
|
406 |
+
For production deployment, additional considerations include:
|
407 |
+
- Continuous model updates and retraining
|
408 |
+
- Human review workflows
|
409 |
+
- Appeal and correction mechanisms
|
410 |
+
- Cross-cultural validation
|
411 |
+
- Regulatory compliance
|
412 |
+
""")
|
413 |
+
|
414 |
+
# Event handlers
|
415 |
+
analyze_btn.click(
|
416 |
+
fn=analyze_text,
|
417 |
+
inputs=text_input,
|
418 |
+
outputs=[status_output, scores_output, counter_narrative_output, processing_info]
|
419 |
+
)
|
420 |
+
|
421 |
+
clear_btn.click(
|
422 |
+
fn=lambda: ("", "", "", "", {}),
|
423 |
+
outputs=[text_input, status_output, counter_narrative_output, processing_info, scores_output]
|
424 |
+
)
|
425 |
+
|
426 |
+
# Launch configuration
|
427 |
+
if __name__ == "__main__":
|
428 |
+
demo.launch(
|
429 |
+
server_name="0.0.0.0",
|
430 |
+
server_port=7860,
|
431 |
+
show_api=False,
|
432 |
+
share=False
|
433 |
+
)
|