chjivan commited on
Commit
1b6f008
·
verified ·
1 Parent(s): abc84da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +631 -326
app.py CHANGED
@@ -1,326 +1,631 @@
1
- import streamlit as st
2
- import torch
3
- from transformers import BertForSequenceClassification, BertTokenizerFast
4
- from transformers import AutoModelForSequenceClassification, AutoTokenizer
5
- import time
6
- import pandas as pd
7
- import base64
8
- from PIL import Image
9
- import io
10
-
11
- # Set page configuration
12
- st.set_page_config(
13
- page_title="SMS Spam Guard",
14
- page_icon="🛡️",
15
- layout="wide",
16
- initial_sidebar_state="expanded"
17
- )
18
-
19
- # Generate SafeTalk logo as base64 (blue shield with "ST" inside)
20
- def create_logo():
21
- from PIL import Image, ImageDraw, ImageFont
22
- import io
23
- import base64
24
-
25
- # Create a new image with a transparent background
26
- img = Image.new('RGBA', (200, 200), color=(0, 0, 0, 0))
27
- draw = ImageDraw.Draw(img)
28
-
29
- # Draw a shield shape
30
- shield_color = (30, 58, 138) # Dark blue
31
-
32
- # Shield outline
33
- points = [(100, 10), (180, 50), (160, 170), (100, 190), (40, 170), (20, 50)]
34
- draw.polygon(points, fill=shield_color)
35
-
36
- # Try to load a font, or use default
37
- try:
38
- font = ImageFont.truetype("arial.ttf", 80)
39
- except IOError:
40
- font = ImageFont.load_default()
41
-
42
- # Add "ST" text in white
43
- draw.text((70, 60), "ST", fill=(255, 255, 255), font=font)
44
-
45
- # Convert to base64 for embedding
46
- buffered = io.BytesIO()
47
- img.save(buffered, format="PNG")
48
- return base64.b64encode(buffered.getvalue()).decode()
49
-
50
- # Custom CSS for styling
51
- st.markdown("""
52
- <style>
53
- .main-header {
54
- font-size: 2.5rem !important;
55
- color: #1E3A8A;
56
- font-weight: 700;
57
- margin-bottom: 0.5rem;
58
- }
59
- .sub-header {
60
- font-size: 1.1rem;
61
- color: #6B7280;
62
- margin-bottom: 2rem;
63
- }
64
- .highlight {
65
- background-color: #F3F4F6;
66
- padding: 1.5rem;
67
- border-radius: 0.5rem;
68
- margin-bottom: 1rem;
69
- }
70
- .result-card {
71
- background-color: #F0F9FF;
72
- padding: 1.5rem;
73
- border-radius: 0.5rem;
74
- border-left: 5px solid #3B82F6;
75
- margin-bottom: 1rem;
76
- }
77
- .spam-alert {
78
- background-color: #FEF2F2;
79
- border-left: 5px solid #EF4444;
80
- }
81
- .ham-alert {
82
- background-color: #ECFDF5;
83
- border-left: 5px solid #10B981;
84
- }
85
- .footer {
86
- text-align: center;
87
- margin-top: 3rem;
88
- font-size: 0.8rem;
89
- color: #9CA3AF;
90
- }
91
- .metrics-container {
92
- display: flex;
93
- justify-content: space-between;
94
- margin-top: 1rem;
95
- }
96
- .metric-item {
97
- text-align: center;
98
- padding: 1rem;
99
- background-color: #F9FAFB;
100
- border-radius: 0.5rem;
101
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
102
- }
103
- .language-tag {
104
- display: inline-block;
105
- padding: 0.25rem 0.5rem;
106
- background-color: #E0E7FF;
107
- color: #4F46E5;
108
- border-radius: 9999px;
109
- font-size: 0.8rem;
110
- font-weight: 500;
111
- margin-right: 0.5rem;
112
- }
113
- </style>
114
- """, unsafe_allow_html=True)
115
-
116
- @st.cache_resource
117
- def load_language_model():
118
- """Load the language detection model"""
119
- model_name = "papluca/xlm-roberta-base-language-detection"
120
- tokenizer = AutoTokenizer.from_pretrained(model_name)
121
- model = AutoModelForSequenceClassification.from_pretrained(model_name)
122
- return tokenizer, model
123
-
124
- @st.cache_resource
125
- def load_spam_model():
126
- """Load the fine-tuned BERT spam detection model"""
127
- model_path = "chjivan/final"
128
- tokenizer = BertTokenizerFast.from_pretrained(model_path)
129
- model = BertForSequenceClassification.from_pretrained(model_path)
130
- return tokenizer, model
131
-
132
- def detect_language(text, tokenizer, model):
133
- """Detect the language of the input text"""
134
- inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
135
- with torch.no_grad():
136
- outputs = model(**inputs)
137
-
138
- # Get predictions and convert to probabilities
139
- logits = outputs.logits
140
- probabilities = torch.softmax(logits, dim=1)[0]
141
-
142
- # Get the predicted language and its probability
143
- predicted_class_id = torch.argmax(probabilities).item()
144
- predicted_language = model.config.id2label[predicted_class_id]
145
- confidence = probabilities[predicted_class_id].item()
146
-
147
- # Get top 3 languages with their probabilities
148
- top_3_indices = torch.topk(probabilities, 3).indices.tolist()
149
- top_3_probs = torch.topk(probabilities, 3).values.tolist()
150
- top_3_langs = [(model.config.id2label[idx], prob) for idx, prob in zip(top_3_indices, top_3_probs)]
151
-
152
- return predicted_language, confidence, top_3_langs
153
-
154
- def classify_spam(text, tokenizer, model):
155
- """Classify the input text as spam or ham"""
156
- inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
157
- with torch.no_grad():
158
- outputs = model(**inputs)
159
-
160
- # Get predictions and convert to probabilities
161
- logits = outputs.logits
162
- probabilities = torch.softmax(logits, dim=1)[0]
163
-
164
- # Get the predicted class and its probability (0: ham, 1: spam)
165
- predicted_class_id = torch.argmax(probabilities).item()
166
- confidence = probabilities[predicted_class_id].item()
167
-
168
- is_spam = predicted_class_id == 1
169
- return is_spam, confidence
170
-
171
- # Generate and cache logo
172
- logo_base64 = create_logo()
173
- logo_html = f'<img src="data:image/png;base64,{logo_base64}" style="height:150px;">'
174
-
175
- # Load both models
176
- with st.spinner("Loading models... This may take a moment."):
177
- lang_tokenizer, lang_model = load_language_model()
178
- spam_tokenizer, spam_model = load_spam_model()
179
-
180
- # App Header with logo
181
- col1, col2 = st.columns([1, 5])
182
- with col1:
183
- st.markdown(logo_html, unsafe_allow_html=True)
184
- with col2:
185
- st.markdown('<h1 class="main-header">SMS Spam Guard</h1>', unsafe_allow_html=True)
186
- st.markdown('<p class="sub-header">智能短信垃圾过滤助手 by SafeTalk Communications Ltd.</p>', unsafe_allow_html=True)
187
-
188
- # Sidebar
189
- with st.sidebar:
190
- st.markdown(logo_html, unsafe_allow_html=True)
191
- st.markdown("### About SafeTalk")
192
- st.markdown("SafeTalk Communications Ltd. provides intelligent communication security solutions to protect users from spam and fraudulent messages.")
193
- st.markdown("#### Our Technology")
194
- st.markdown("- ✅ Advanced AI-powered spam detection")
195
- st.markdown("- 🌐 Multi-language support")
196
- st.markdown("- 🔒 Secure and private processing")
197
- st.markdown("- Real-time analysis")
198
-
199
- st.markdown("---")
200
- st.markdown("### Sample Messages")
201
-
202
- if st.button("Sample Spam (English)"):
203
- st.session_state.sms_input = "URGENT: You have won a $1,000 Walmart gift card. Go to http://bit.ly/claim-prize to claim now before it expires!"
204
-
205
- if st.button("Sample Legitimate (English)"):
206
- st.session_state.sms_input = "Your Amazon package will be delivered today. Thanks for ordering from Amazon!"
207
-
208
- if st.button("Sample Message (French)"):
209
- st.session_state.sms_input = "Bonjour! Votre réservation pour le restaurant est confirmée pour ce soir à 20h. À bientôt!"
210
-
211
- if st.button("Sample Message (Spanish)"):
212
- st.session_state.sms_input = "Hola, tu cita médica está programada para mañana a las 10:00. Por favor llega 15 minutos antes."
213
-
214
- # Main Content
215
- st.markdown('<div class="highlight">', unsafe_allow_html=True)
216
- # Input form
217
- sms_input = st.text_area(
218
- "Enter the SMS message to analyze:",
219
- value=st.session_state.get("sms_input", ""),
220
- height=100,
221
- key="sms_input",
222
- help="Enter the SMS message you want to analyze for spam"
223
- )
224
-
225
- analyze_button = st.button("📱 Analyze Message", use_container_width=True)
226
- st.markdown('</div>', unsafe_allow_html=True)
227
-
228
- # Process input and display results
229
- if analyze_button and sms_input:
230
- with st.spinner("Analyzing message..."):
231
- # Step 1: Language Detection
232
- lang_start_time = time.time()
233
- lang_code, lang_confidence, top_langs = detect_language(sms_input, lang_tokenizer, lang_model)
234
- lang_time = time.time() - lang_start_time
235
-
236
- # Create mapping for full language names
237
- lang_names = {
238
- "ar": "Arabic",
239
- "bg": "Bulgarian",
240
- "de": "German",
241
- "el": "Greek",
242
- "en": "English",
243
- "es": "Spanish",
244
- "fr": "French",
245
- "hi": "Hindi",
246
- "it": "Italian",
247
- "ja": "Japanese",
248
- "nl": "Dutch",
249
- "pl": "Polish",
250
- "pt": "Portuguese",
251
- "ru": "Russian",
252
- "sw": "Swahili",
253
- "th": "Thai",
254
- "tr": "Turkish",
255
- "ur": "Urdu",
256
- "vi": "Vietnamese",
257
- "zh": "Chinese"
258
- }
259
-
260
- lang_name = lang_names.get(lang_code, lang_code)
261
-
262
- # Step 2: Spam Classification
263
- spam_start_time = time.time()
264
- is_spam, spam_confidence = classify_spam(sms_input, spam_tokenizer, spam_model)
265
- spam_time = time.time() - spam_start_time
266
-
267
- # Display Language Detection Results
268
- st.markdown("### Analysis Results")
269
-
270
- col1, col2 = st.columns(2)
271
-
272
- with col1:
273
- st.markdown("#### 📊 Language Detection")
274
- st.markdown(f'<div class="result-card">', unsafe_allow_html=True)
275
- st.markdown(f'<span class="language-tag">{lang_name}</span> Detected with {lang_confidence:.1%} confidence', unsafe_allow_html=True)
276
-
277
- # Display top 3 languages
278
- st.markdown("##### Top language probabilities:")
279
- for lang_code, prob in top_langs:
280
- lang_full = lang_names.get(lang_code, lang_code)
281
- st.markdown(f"- {lang_full}: {prob:.1%}")
282
-
283
- st.markdown(f"⏱️ Processing time: {lang_time:.3f} seconds")
284
- st.markdown('</div>', unsafe_allow_html=True)
285
-
286
- with col2:
287
- st.markdown("#### 🔍 Spam Detection")
288
-
289
- if is_spam:
290
- st.markdown(f'<div class="result-card spam-alert">', unsafe_allow_html=True)
291
- st.markdown(f"⚠️ **SPAM DETECTED** with {spam_confidence:.1%} confidence")
292
- st.markdown("This message appears to be spam and potentially harmful.")
293
- else:
294
- st.markdown(f'<div class="result-card ham-alert">', unsafe_allow_html=True)
295
- st.markdown(f"✅ **LEGITIMATE MESSAGE** with {spam_confidence:.1%} confidence")
296
- st.markdown("This message appears to be legitimate.")
297
-
298
- st.markdown(f"⏱️ Processing time: {spam_time:.3f} seconds")
299
- st.markdown('</div>', unsafe_allow_html=True)
300
-
301
- # Summary and Recommendations
302
- st.markdown("### 📋 Summary & Recommendations")
303
- if is_spam:
304
- st.warning("📵 **Recommended Action**: This message should be blocked or moved to spam folder.")
305
- st.markdown("""
306
- **Why this is likely spam:**
307
- - Contains suspicious language patterns
308
- - May include urgent calls to action
309
- - Could contain unsolicited offers
310
- """)
311
- else:
312
- st.success("✅ **Recommended Action**: This message can be delivered to the inbox.")
313
-
314
- # Chart for visualization
315
- st.markdown("### 📈 Confidence Visualization")
316
- chart_data = pd.DataFrame({
317
- 'Task': ['Language Detection', 'Spam Classification'],
318
- 'Confidence': [lang_confidence, spam_confidence if is_spam else 1-spam_confidence]
319
- })
320
- st.bar_chart(chart_data.set_index('Task'))
321
-
322
- # Footer
323
- st.markdown('<div class="footer">', unsafe_allow_html=True)
324
- st.markdown("© 2023 SafeTalk Communications Ltd. | www.safetalk.com")
325
- st.markdown("SMS Spam Guard is an intelligent message filtering solution to protect users from unwanted communications.")
326
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import torch
3
+ from transformers import BertForSequenceClassification, BertTokenizerFast
4
+ from transformers import AutoModelForSequenceClassification, AutoTokenizer
5
+ import time
6
+ import pandas as pd
7
+ import base64
8
+ from PIL import Image, ImageDraw, ImageFont
9
+ import io
10
+ import streamlit.components.v1 as components
11
+
12
+ # Set page configuration
13
+ st.set_page_config(
14
+ page_title="SMS Spam Guard",
15
+ page_icon="🛡️",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded"
18
+ )
19
+
20
+ # New function to create a tech-themed Spam Guard logo
21
+ def create_spam_guard_logo():
22
+ width, height = 200, 200
23
+ img = Image.new('RGBA', (width, height), (0,0,0,0)) # Transparent background
24
+ draw = ImageDraw.Draw(img)
25
+
26
+ # Flat Design Colors (slightly adjusted for modern flat look)
27
+ primary_blue = (20, 120, 220) # A strong, modern blue
28
+ accent_green = (0, 200, 150) # A vibrant, techy teal/green
29
+ light_accent_blue = (100, 180, 240) # Lighter blue for highlights or secondary elements
30
+ white_color = (255, 255, 255)
31
+ dark_gray_text = (50, 50, 50) # For subtle text if needed
32
+
33
+ # Background: A subtle gradient or a clean shape
34
+ # Option 1: Clean circle as base
35
+ # draw.ellipse([(10, 10), (width - 10, height - 10)], fill=primary_blue)
36
+
37
+ # Option 2: Modern, slightly rounded rectangle or abstract shape
38
+ # For a more abstract, less shield-like, but still contained feel:
39
+ # Let's try a stylized hexagon or a shape made of intersecting elements.
40
+
41
+ # Design: Abstract interlocking shapes suggesting SG or a data block / shield
42
+ # Main body - a dynamic shape
43
+ path = [
44
+ (width * 0.15, height * 0.2), (width * 0.85, height * 0.2),
45
+ (width * 0.75, height * 0.8), (width * 0.25, height * 0.8)
46
+ ]
47
+ draw.polygon(path, fill=primary_blue)
48
+
49
+ # Accent element (e.g., a stylized 'S' or a connecting line)
50
+ draw.line([
51
+ (width * 0.3, height * 0.35),
52
+ (width * 0.7, height * 0.35),
53
+ (width * 0.7, height * 0.5),
54
+ (width * 0.3, height * 0.5),
55
+ (width * 0.3, height * 0.65),
56
+ (width * 0.7, height * 0.65)
57
+ ], fill=accent_green, width=18, joint="miter")
58
+
59
+ # Adding a subtle highlight or secondary shape for depth (still flat)
60
+ draw.polygon([
61
+ (width * 0.18, height * 0.22), (width * 0.82, height * 0.22),
62
+ (width * 0.72, height * 0.78), (width * 0.28, height * 0.78)
63
+ ], outline=light_accent_blue, width=4)
64
+
65
+ # Text "SG" - Clean, modern, sans-serif font
66
+ try:
67
+ # Attempt to load a more modern, geometric font if available
68
+ # For example, 'Montserrat-Bold.ttf' or 'Roboto-Medium.ttf'
69
+ # If not, Arial Bold is a safe fallback.
70
+ font = ImageFont.truetype("arialbd.ttf", 70) # Arial Bold as a fallback
71
+ except IOError:
72
+ font = ImageFont.load_default() # Fallback
73
+
74
+ text = "SG"
75
+ text_bbox = draw.textbbox((0,0), text, font=font)
76
+ text_width = text_bbox[2] - text_bbox[0]
77
+ text_height = text_bbox[3] - text_bbox[1]
78
+
79
+ text_x = (width - text_width) / 2
80
+ # text_y = (height - text_height) / 2
81
+ # Slightly adjust y if the accent green takes up visual center
82
+ text_y = (height - text_height) / 2 + 5 # Adjusted to better center with the green shape
83
+
84
+ # Make text white and prominent
85
+ draw.text((text_x, text_y), text, font=font, fill=white_color)
86
+
87
+ buffered = io.BytesIO()
88
+ img.save(buffered, format="PNG")
89
+ img_str = base64.b64encode(buffered.getvalue()).decode()
90
+ return f"data:image/png;base64,{img_str}"
91
+
92
+ # Custom CSS for styling with China Mobile colors
93
+ st.markdown("""
94
+ <style>
95
+ :root {
96
+ --cm-blue: #0072d0; /* 调深主蓝色 */
97
+ --cm-light-blue: #4aa3df;
98
+ --cm-green: #00a651; /* 调深绿色 */
99
+ --cm-bright-green: #70c050; /* 调整亮绿色 */
100
+ --cm-dark-blue: #004080; /* 更深的蓝色 */
101
+ --cm-gray: #f5f7fa;
102
+ --text-color: #222222; /* 更深的文本颜色 */
103
+ --card-bg: rgba(255, 255, 255, 0.92); /* 增加不透明度 */
104
+ --card-border: rgba(180, 180, 180, 0.5); /* 更明显的边框 */
105
+ }
106
+
107
+ body {
108
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
109
+ line-height: 1.6;
110
+ color: var(--text-color);
111
+ -webkit-font-smoothing: antialiased; /* 改善文字渲染 */
112
+ -moz-osx-font-smoothing: grayscale; /* 改善文字渲染 */
113
+ }
114
+
115
+ .stApp {
116
+ background: linear-gradient(135deg, #c4e3ff 0%, #e8f4ff 60%, #ddf5dd 100%); /* 更亮的背景以增加对比度 */
117
+ }
118
+
119
+ /* 标题强化样式 - 提高对比度 */
120
+ .main-header {
121
+ font-size: 3.2rem !important;
122
+ font-weight: 800 !important;
123
+ margin-bottom: 0.3rem;
124
+ color: var(--cm-dark-blue); /* 纯色替代渐变 */
125
+ text-shadow: none; /* 移除阴影以增加锐利度 */
126
+ -webkit-text-fill-color: var(--cm-dark-blue); /* 覆盖之前的透明设置 */
127
+ -moz-text-fill-color: var(--cm-dark-blue);
128
+ text-fill-color: var(--cm-dark-blue);
129
+ letter-spacing: -0.5px;
130
+ }
131
+
132
+ .sub-header {
133
+ font-size: 1.25rem;
134
+ color: #333333; /* 更深的颜色 */
135
+ margin-bottom: 2rem;
136
+ font-weight: 500;
137
+ }
138
+
139
+ /* 各组件样式强化 - 更高对比度 */
140
+ .highlight {
141
+ background-color: var(--card-bg);
142
+ padding: 1.8rem;
143
+ border-radius: 16px;
144
+ margin-bottom: 1.5rem;
145
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); /* 更深的阴影 */
146
+ transition: all 0.3s ease-out;
147
+ border: 1px solid var(--card-border);
148
+ }
149
+ .highlight:hover {
150
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18);
151
+ transform: translateY(-3px);
152
+ }
153
+ .result-card, .card-gradient, .card-shadow {
154
+ padding: 1.8rem;
155
+ border-radius: 16px;
156
+ margin-bottom: 1.2rem;
157
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
158
+ transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
159
+ border: 1px solid var(--card-border);
160
+ background-color: white; /* 纯白色背景 */
161
+ }
162
+ .result-card:hover, .card-gradient:hover, .card-shadow:hover {
163
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18);
164
+ transform: translateY(-4px) scale(1.01);
165
+ }
166
+ .spam-alert {
167
+ background: #fff0f0; /* 移除渐变,使用纯色 */
168
+ border-left: 8px solid #EF4444;
169
+ color: #aa0000; /* 更深的红色 */
170
+ }
171
+ .ham-alert {
172
+ background: #f0fff0; /* 移除渐变,使用纯色 */
173
+ border-left: 8px solid var(--cm-green);
174
+ color: #005500; /* 更深的绿色 */
175
+ }
176
+
177
+ /* 页脚加强 */
178
+ .footer {
179
+ text-align: center;
180
+ margin-top: 4rem;
181
+ padding-top: 2rem;
182
+ border-top: 1px solid var(--card-border);
183
+ font-size: 0.9rem;
184
+ color: #444444; /* 更深的灰色 */
185
+ }
186
+
187
+ /* 标签样式加强 */
188
+ .language-tag {
189
+ display: inline-block;
190
+ padding: 0.4rem 1rem;
191
+ background-color: var(--cm-dark-blue); /* 更深的蓝色 */
192
+ color: white;
193
+ border-radius: 20px;
194
+ font-size: 0.95rem;
195
+ font-weight: 600;
196
+ margin-right: 0.6rem;
197
+ box-shadow: 0 3px 6px rgba(0, 64, 128, 0.3); /* 更深的阴影 */
198
+ }
199
+
200
+ /* 按钮样式加强 */
201
+ .stButton > button {
202
+ border-radius: 12px;
203
+ padding: 0.7rem 1.4rem;
204
+ font-weight: 600;
205
+ transition: all 0.25s ease;
206
+ box-shadow: 0 4px 8px rgba(0,0,0,0.12);
207
+ font-size: 1rem;
208
+ border: 1px solid rgba(0,0,0,0.1); /* 添加边框 */
209
+ }
210
+ .stButton > button:hover {
211
+ transform: translateY(-3px);
212
+ box-shadow: 0 6px 12px rgba(0,0,0,0.15);
213
+ }
214
+
215
+ /* 分析按钮更加突出 */
216
+ div.stButton[key="analyze_btn"] > button,
217
+ button[kind="primary"]
218
+ {
219
+ background: var(--cm-blue); /* 纯色替代渐变 */
220
+ color: white !important;
221
+ font-weight: 700;
222
+ padding: 0.8rem 1.5rem;
223
+ font-size: 1.1rem;
224
+ letter-spacing: 0.5px;
225
+ text-transform: uppercase;
226
+ border: none;
227
+ }
228
+ div.stButton[key="analyze_btn"] > button:hover {
229
+ background: var(--cm-dark-blue);
230
+ box-shadow: 0 8px 15px rgba(0, 64, 128, 0.3);
231
+ }
232
+
233
+ /* 侧边栏按钮样式 */
234
+ .sidebar .stButton > button {
235
+ background-color: var(--cm-green);
236
+ text-transform: none;
237
+ }
238
+ .sidebar .stButton > button:hover {
239
+ background-color: var(--cm-bright-green);
240
+ }
241
+
242
+ /* 文本框样式加强 */
243
+ div.stTextArea > div > div > textarea {
244
+ border-color: #5599dd; /* 更鲜明的边框颜色 */
245
+ border-width: 2px;
246
+ border-radius: 12px;
247
+ background-color: white; /* 纯白色背景 */
248
+ font-size: 1.05rem;
249
+ padding: 12px;
250
+ color: #222; /* 确保文本为深色 */
251
+ }
252
+ div.stTextArea > div > div > textarea:focus {
253
+ border-color: var(--cm-blue);
254
+ box-shadow: 0 0 0 4px rgba(0, 114, 208, 0.25);
255
+ background-color: white;
256
+ }
257
+
258
+ /* 玻璃效果加强 - 减少模糊,增加清晰度 */
259
+ .glass-effect {
260
+ background: rgba(255, 255, 255, 0.92); /* 增加不透明度 */
261
+ backdrop-filter: blur(6px); /* 减少模糊程度 */
262
+ border-radius: 20px;
263
+ border: 1px solid rgba(255, 255, 255, 0.7); /* 更明显的边框 */
264
+ transition: all 0.3s ease-in-out;
265
+ box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15);
266
+ }
267
+ .glass-effect:hover {
268
+ box-shadow: 0 15px 30px rgba(31, 38, 135, 0.25);
269
+ transform: translateY(-5px);
270
+ }
271
+
272
+ /* 加载动画加强 */
273
+ .loading-animation-container svg {
274
+ animation: spin 1.2s linear infinite, pulse-opacity 1.2s ease-in-out infinite alternate;
275
+ transform-origin: center center;
276
+ width: 70px;
277
+ height: 70px;
278
+ filter: drop-shadow(0 0 2px rgba(0,0,0,0.2)); /* 添加轻微阴影 */
279
+ }
280
+
281
+ /* 图表样式加强 */
282
+ .stChart > div > div > div > svg g rect {
283
+ fill: var(--cm-blue) !important;
284
+ transition: fill 0.3s ease;
285
+ }
286
+ .stChart > div > div > div > svg g rect:hover {
287
+ fill: var(--cm-dark-blue) !important;
288
+ }
289
+
290
+ /* 结果文本加强 - 提高对比度 */
291
+ .result-content {
292
+ font-size: 1.1rem;
293
+ color: #222; /* 确保深色文本 */
294
+ }
295
+ .result-value {
296
+ font-size: 1.2rem;
297
+ font-weight: 700;
298
+ color: var(--cm-dark-blue);
299
+ }
300
+ .result-title {
301
+ font-size: 1.4rem;
302
+ font-weight: 700;
303
+ color: var(--cm-dark-blue);
304
+ margin-bottom: 15px;
305
+ letter-spacing: 0.5px;
306
+ }
307
+
308
+ /* 强调主要文字内容 - 提高对比度 */
309
+ h3 {
310
+ font-size: 1.8rem !important;
311
+ font-weight: 700 !important;
312
+ color: var(--cm-dark-blue) !important;
313
+ letter-spacing: -0.5px;
314
+ margin-bottom: 20px !important;
315
+ }
316
+ h4 {
317
+ font-size: 1.4rem !important;
318
+ font-weight: 700 !important;
319
+ letter-spacing: -0.3px;
320
+ color: #222 !important;
321
+ }
322
+ h5 {
323
+ font-size: 1.2rem !important;
324
+ font-weight: 600 !important;
325
+ color: #333 !important;
326
+ }
327
+ p, li {
328
+ font-size: 1.05rem !important;
329
+ color: #333 !important;
330
+ }
331
+ strong {
332
+ font-weight: 700;
333
+ color: var(--cm-dark-blue);
334
+ }
335
+ </style>
336
+ """, unsafe_allow_html=True)
337
+
338
+ @st.cache_resource
339
+ def load_language_model():
340
+ """Load the language detection model"""
341
+ model_name = "papluca/xlm-roberta-base-language-detection"
342
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
343
+ model = AutoModelForSequenceClassification.from_pretrained(model_name)
344
+ return tokenizer, model
345
+
346
+ @st.cache_resource
347
+ def load_spam_model():
348
+ """Load the fine-tuned BERT spam detection model"""
349
+ model_path = "chjivan/final"
350
+ tokenizer = BertTokenizerFast.from_pretrained(model_path)
351
+ model = BertForSequenceClassification.from_pretrained(model_path)
352
+ return tokenizer, model
353
+
354
+ def detect_language(text, tokenizer, model):
355
+ """Detect the language of the input text"""
356
+ inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
357
+ with torch.no_grad():
358
+ outputs = model(**inputs)
359
+
360
+ logits = outputs.logits
361
+ probabilities = torch.softmax(logits, dim=1)[0]
362
+
363
+ predicted_class_id = torch.argmax(probabilities).item()
364
+ predicted_language = model.config.id2label[predicted_class_id]
365
+ confidence = probabilities[predicted_class_id].item()
366
+
367
+ top_3_indices = torch.topk(probabilities, 3).indices.tolist()
368
+ top_3_probs = torch.topk(probabilities, 3).values.tolist()
369
+ top_3_langs = [(model.config.id2label[idx], prob) for idx, prob in zip(top_3_indices, top_3_probs)]
370
+
371
+ return predicted_language, confidence, top_3_langs
372
+
373
+ def classify_spam(text, tokenizer, model):
374
+ """Classify the input text as spam or ham"""
375
+ inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True, max_length=128)
376
+ with torch.no_grad():
377
+ outputs = model(**inputs)
378
+
379
+ logits = outputs.logits
380
+ probabilities = torch.softmax(logits, dim=1)[0]
381
+
382
+ predicted_class_id = torch.argmax(probabilities).item()
383
+ confidence = probabilities[predicted_class_id].item()
384
+
385
+ is_spam = predicted_class_id == 1
386
+ return is_spam, confidence
387
+
388
+ # Get the new Spam Guard logo
389
+ logo_data = create_spam_guard_logo()
390
+
391
+ # Add custom CSS animations (ensure this is defined before use)
392
+ st.markdown("""
393
+ <style>
394
+ @keyframes fadeIn {
395
+ from { opacity: 0; transform: translateY(20px); }
396
+ to { opacity: 1; transform: translateY(0); }
397
+ }
398
+
399
+ @keyframes pulse {
400
+ 0% { transform: scale(1); }
401
+ 50% { transform: scale(1.03); }
402
+ 100% { transform: scale(1); }
403
+ }
404
+
405
+ @keyframes slideInLeft {
406
+ from { opacity: 0; transform: translateX(-30px); }
407
+ to { opacity: 1; transform: translateX(0); }
408
+ }
409
+
410
+ @keyframes slideInRight {
411
+ from { opacity: 0; transform: translateX(30px); }
412
+ to { opacity: 1; transform: translateX(0); }
413
+ }
414
+
415
+ .animated-fadeIn {
416
+ animation: fadeIn 0.8s ease-out forwards;
417
+ }
418
+
419
+ .animated-pulse-subtle {
420
+ animation: pulse 2.5s infinite ease-in-out;
421
+ }
422
+
423
+ .animated-slideInLeft {
424
+ animation: slideInLeft 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
425
+ }
426
+
427
+ .animated-slideInRight {
428
+ animation: slideInRight 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
429
+ }
430
+
431
+ .neon-text {
432
+ text-shadow: 0 0 3px rgba(0, 134, 209, 0.2), 0 0 6px rgba(0, 134, 209, 0.15);
433
+ }
434
+ </style>
435
+ """, unsafe_allow_html=True)
436
+
437
+ # Load both models
438
+ with st.spinner("Loading models... This may take a moment."):
439
+ lang_tokenizer, lang_model = load_language_model()
440
+ spam_tokenizer, spam_model = load_spam_model()
441
+
442
+ # App Header with new logo
443
+ st.markdown(f"""
444
+ <div style="display: flex; align-items: center; margin-bottom: 2.5rem; padding: 1.5rem; background: rgba(255,255,255,0.92); border-radius: 20px; box-shadow: 0 8px 24px rgba(0,0,0,0.12);" class="animated-fadeIn">
445
+ <img src="{logo_data}" style="height: 100px; margin-right: 30px; border-radius: 16px; box-shadow: 0 5px 15px rgba(0,0,0,0.15);" class="animated-pulse-subtle">
446
+ <div>
447
+ <h1 class="main-header">SMS Spam Guard</h1>
448
+ <p class="sub-header">Intelligent SMS Filtering Assistant by China Mobile Communications Group Co.,Ltd</p>
449
+ </div>
450
+ </div>
451
+ """, unsafe_allow_html=True)
452
+
453
+ # Create a two-column layout
454
+ col1, col2 = st.columns([1, 2]) # Adjusted column ratio for better balance
455
+
456
+ # Sidebar content in col1 (styled as a card)
457
+ with col1:
458
+ st.markdown(f"""
459
+ <div class="glass-effect animated-slideInLeft" style="padding: 25px; border-radius: 12px; margin-bottom: 25px; animation-delay: 0.1s;">
460
+ <img src="{logo_data}" style="width: 60%; margin: 0 auto 20px auto; display: block; border-radius: 8px;">
461
+ <h3 style="color: var(--cm-blue); text-align: center; margin-bottom:15px;">About Us</h3>
462
+ <p style="font-size:0.9rem; color: #444;">China Mobile Communications Group Co.,Ltd provides intelligent communication security solutions to protect users from spam and fraudulent messages.</p>
463
+ </div>
464
+ """, unsafe_allow_html=True)
465
+
466
+ st.markdown("""
467
+ <div class="glass-effect animated-slideInLeft" style="padding: 25px; border-radius: 12px; margin-bottom: 25px; animation-delay: 0.2s;">
468
+ <h4 style="color: var(--cm-blue); margin-bottom:15px;">Our Technology</h4>
469
+ <ul style="padding-left: 20px; font-size:0.9rem; color: #444;">
470
+ <li style="margin-bottom:8px;">✅ Advanced AI-powered spam detection</li>
471
+ <li style="margin-bottom:8px;">🌐 Multi-language support</li>
472
+ <li style="margin-bottom:8px;">🔒 Secure and private processing</li>
473
+ <li>⚡ Real-time analysis</li>
474
+ </ul>
475
+ </div>
476
+ """, unsafe_allow_html=True)
477
+
478
+ st.markdown("<h4 style='color: var(--cm-blue); margin-left: 5px; margin-bottom:10px;' class='animated-slideInLeft' data-animation-delay='0.3s'>Sample Messages</h4>", unsafe_allow_html=True)
479
+
480
+ # Sample buttons with unique keys and improved help text
481
+ button_style = "margin-bottom: 8px; width: 100%;"
482
+ if st.button("Sample Spam (English)", key="spam_btn_en", help="Load a sample English spam message", type="secondary"):
483
+ st.session_state.sms_input = "URGENT: You have won a $1,000 Walmart gift card. Go to http://bit.ly/claim-prize to claim now before it expires!"
484
+
485
+ if st.button("Sample Legitimate (English)", key="ham_btn_en", help="Load a sample English legitimate message", type="secondary"):
486
+ st.session_state.sms_input = "Your Amazon package will be delivered today. Thanks for ordering from Amazon!"
487
+
488
+ if st.button("Sample Message (French)", key="french_btn_fr", help="Load a sample French message", type="secondary"):
489
+ st.session_state.sms_input = "Bonjour! Votre réservation pour le restaurant est confirmée pour ce soir à 20h. À bientôt!"
490
+
491
+ if st.button("Sample Message (Spanish)", key="spanish_btn_es", help="Load a sample Spanish message", type="secondary"):
492
+ st.session_state.sms_input = "Hola, tu cita médica está programada para mañana a las 10:00. Por favor llega 15 minutos antes."
493
+
494
+ # Main content in col2
495
+ with col2:
496
+ st.markdown("""
497
+ <div class="glass-effect animated-fadeIn" style="padding: 30px; border-radius: 16px; margin-bottom: 30px; animation-delay: 0.1s;">
498
+ <h3 style="color: var(--cm-blue); margin-bottom: 20px; text-align:center;">Analyze Your Message</h3>
499
+ </div>
500
+ """, unsafe_allow_html=True)
501
+
502
+ sms_input = st.text_area(
503
+ "", # Label removed for cleaner look, relying on header
504
+ value=st.session_state.get("sms_input", ""),
505
+ height=120, # Increased height
506
+ key="sms_input",
507
+ placeholder="Enter the SMS message you want to analyze here...",
508
+ help="Paste or type the SMS message to check if it's spam or legitimate."
509
+ )
510
+
511
+ analyze_button = st.button("📱 Analyze Message", use_container_width=True, key="analyze_btn", type="primary")
512
+
513
+ if analyze_button and sms_input:
514
+ with st.spinner(""):
515
+ st.markdown("""
516
+ <div class="loading-animation-container animated-fadeIn" style="text-align: center; color: var(--cm-blue); margin: 25px 0;">
517
+ <svg width="60" height="60" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
518
+ <path d="M12 2C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2V2Z" stroke="var(--cm-blue)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
519
+ <path d="M12 2C6.47715 2 2 6.47715 2 12H2Z" stroke="var(--cm-light-blue)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
520
+ </svg>
521
+ <p style="font-size: 1.1rem; margin-top: 10px;">Analyzing your message...</p>
522
+ </div>
523
+ """, unsafe_allow_html=True)
524
+ time.sleep(0.5) # Simulate some work
525
+
526
+ lang_start_time = time.time()
527
+ lang_code, lang_confidence, top_langs = detect_language(sms_input, lang_tokenizer, lang_model)
528
+ lang_time = time.time() - lang_start_time
529
+
530
+ lang_names = {
531
+ "ar": "Arabic", "bg": "Bulgarian", "de": "German", "el": "Greek", "en": "English",
532
+ "es": "Spanish", "fr": "French", "hi": "Hindi", "it": "Italian", "ja": "Japanese",
533
+ "nl": "Dutch", "pl": "Polish", "pt": "Portuguese", "ru": "Russian", "sw": "Swahili",
534
+ "th": "Thai", "tr": "Turkish", "ur": "Urdu", "vi": "Vietnamese", "zh": "Chinese"
535
+ }
536
+ lang_name = lang_names.get(lang_code, lang_code.capitalize())
537
+
538
+ spam_start_time = time.time()
539
+ is_spam, spam_confidence = classify_spam(sms_input, spam_tokenizer, spam_model)
540
+ spam_time = time.time() - spam_start_time
541
+
542
+ st.markdown("<h3 style='color: var(--cm-blue); margin-top: 30px; text-align:center;' class='animated-fadeIn'>Analysis Results</h3>", unsafe_allow_html=True)
543
+
544
+ res_col1, res_col2 = st.columns(2)
545
+
546
+ with res_col1:
547
+ st.markdown(f"""
548
+ <div class="result-card animated-slideInLeft" style="animation-delay: 0.2s; background: linear-gradient(135deg, #e6f7ff 0%, #f0faff 100%);">
549
+ <h4 class="result-title">📊 Language Detection</h4>
550
+ <div class="result-content">
551
+ <div style="display: flex; align-items: center; margin-bottom: 15px;">
552
+ <span class="language-tag">{lang_name}</span>
553
+ <span>Detected with <span class="result-value">{lang_confidence:.1%}</span> confidence</span>
554
+ </div>
555
+ <h5 style="color: var(--cm-dark-blue); margin-bottom: 10px; font-size:0.95rem;">Top language probabilities:</h5>
556
+ <ul style="font-size:0.9rem; padding-left:18px;">
557
+ """, unsafe_allow_html=True)
558
+ for l_code, l_prob in top_langs:
559
+ st.markdown(f"<li>{lang_names.get(l_code, l_code.capitalize())}: {l_prob:.1%}</li>", unsafe_allow_html=True)
560
+ st.markdown(f"""
561
+ </ul>
562
+ <p style="margin-top: 15px; font-size:0.85rem; color:#555;">⏱️ Processing time: {lang_time:.3f} seconds</p>
563
+ </div>
564
+ """, unsafe_allow_html=True)
565
+
566
+ with res_col2:
567
+ result_confidence = spam_confidence if is_spam else (1 - spam_confidence)
568
+ if is_spam:
569
+ st.markdown(f"""
570
+ <div class="result-card spam-alert animated-slideInRight" style="animation-delay: 0.3s;">
571
+ <h4 class="result-title">🔍 Spam Detection</h4>
572
+ <div class="result-content">
573
+ <div style="font-size: 1.1rem; font-weight: bold; color: #b91c1c; margin-bottom: 10px;">⚠️ SPAM DETECTED</div>
574
+ <p style="margin-bottom:10px;">Confidence: <span class="result-value">{result_confidence:.1%}</span></p>
575
+ <p style="font-size:0.9rem;">This message shows strong indicators of being spam.</p>
576
+ <p style="margin-top: 15px; font-size:0.85rem; color:#555;">⏱️ Processing time: {spam_time:.3f} seconds</p>
577
+ </div>
578
+ </div>
579
+ """, unsafe_allow_html=True)
580
+ else:
581
+ st.markdown(f"""
582
+ <div class="result-card ham-alert animated-slideInRight" style="animation-delay: 0.3s;">
583
+ <h4 class="result-title">🔍 Spam Detection</h4>
584
+ <div class="result-content">
585
+ <div style="font-size: 1.1rem; font-weight: bold; color: #047857; margin-bottom: 10px;">✅ LEGITIMATE MESSAGE</div>
586
+ <p style="margin-bottom:10px;">Confidence: <span class="result-value">{result_confidence:.1%}</span></p>
587
+ <p style="font-size:0.9rem;">This message appears to be legitimate.</p>
588
+ <p style="margin-top: 15px; font-size:0.85rem; color:#555;">⏱️ Processing time: {spam_time:.3f} seconds</p>
589
+ </div>
590
+ </div>
591
+ """, unsafe_allow_html=True)
592
+
593
+ st.markdown("""
594
+ <div class="glass-effect animated-fadeIn" style="padding: 25px; border-radius: 12px; margin: 30px 0; animation-delay: 0.4s;">
595
+ <h3 style="color: var(--cm-blue); margin-bottom: 15px; text-align:center;">📋 Summary & Recommendations</h3>
596
+ """, unsafe_allow_html=True)
597
+ if is_spam:
598
+ st.warning("📵 **Recommended Action**: This message should be treated with caution, blocked, or moved to the spam folder.")
599
+ st.markdown("""
600
+ **Potential reasons for spam classification:**
601
+ <ul style="font-size:0.9rem;">
602
+ <li>Contains suspicious language patterns or urgency.</li>
603
+ <li>May include unsolicited offers or links to untrusted sites.</li>
604
+ <li>Resembles known spam message structures.</li>
605
+ </ul>
606
+ """, unsafe_allow_html=True)
607
+ else:
608
+ st.success("✅ **Recommended Action**: This message seems safe and can be delivered to the inbox.")
609
+ st.markdown("</div>", unsafe_allow_html=True)
610
+
611
+ st.markdown("""
612
+ <div class="glass-effect animated-fadeIn" style="padding: 25px; border-radius: 12px; margin: 30px 0; animation-delay: 0.5s;">
613
+ <h3 style="color: var(--cm-blue); margin-bottom: 20px; text-align:center;">📈 Confidence Visualization</h3>
614
+ """, unsafe_allow_html=True)
615
+ chart_data = pd.DataFrame({
616
+ 'Task': ['Language Detection Confidence', 'Spam Classification Certainty'],
617
+ 'Confidence': [lang_confidence, result_confidence]
618
+ })
619
+ st.bar_chart(chart_data.set_index('Task'), height=300, use_container_width=True)
620
+ st.markdown("</div>", unsafe_allow_html=True)
621
+
622
+ # Footer
623
+ st.markdown(f"""
624
+ <div class="footer animated-fadeIn" style="animation-delay: 0.8s;">
625
+ <div style="display: flex; justify-content: center; align-items: center; margin-bottom: 10px;">
626
+ <img src="{logo_data}" style="height: 25px; margin-right: 12px; border-radius:4px;">
627
+ <span>© {time.strftime('%Y')} China Mobile Communications Group Co.,Ltd | <a href="http://www.chinamobile.com" target="_blank" style="color: var(--cm-blue); text-decoration:none;">www.chinamobile.com</a></span>
628
+ </div>
629
+ <p style="font-size:0.8rem; color:#888;">SMS Spam Guard: Your intelligent shield against unwanted communications.</p>
630
+ </div>
631
+ """, unsafe_allow_html=True)