Update app.py
Browse files
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 |
-
|
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 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|