vanhai123 commited on
Commit
2e5eaa3
·
verified ·
1 Parent(s): 4db6dda

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +523 -357
app.py CHANGED
@@ -1,358 +1,524 @@
1
- import gradio as gr
2
- from transformers import pipeline
3
- import numpy as np
4
- import plotly.express as px
5
- import plotly.graph_objects as go
6
-
7
- # Load mô hình từ Hugging Face Model Hub
8
- pipe = pipeline("text-classification", model="vanhai123/phobert-vi-comment-4class", tokenizer="vanhai123/phobert-vi-comment-4class")
9
-
10
- # Map nhãn lại cho dễ đọc với emoji và màu sắc
11
- label_map = {
12
- "LABEL_0": {"name": "Tích cực", "emoji": "😊", "color": "#22c55e"},
13
- "LABEL_1": {"name": "Tiêu cực", "emoji": "😞", "color": "#ef4444"},
14
- "LABEL_2": {"name": "Trung tính", "emoji": "😐", "color": "#64748b"},
15
- "LABEL_3": {"name": "Độc hại", "emoji": "😡", "color": "#dc2626"}
16
- }
17
-
18
- def classify_comment(comment):
19
- if not comment.strip():
20
- return "Vui lòng nhập bình luận để phân tích!", None, None
21
-
22
- # Lấy tất cả kết quả dự đoán
23
- results = pipe(comment)
24
- if isinstance(results, list):
25
- results = results[0] if results else {}
26
-
27
- # Lấy kết quả chi tiết cho tất cả các nhãn
28
- all_scores = pipe(comment, return_all_scores=True)
29
- if isinstance(all_scores, list):
30
- all_scores = all_scores[0] if all_scores else []
31
-
32
- # Tạo kết quả chính
33
- main_label = results.get('label', 'UNKNOWN')
34
- main_score = results.get('score', 0)
35
-
36
- if main_label in label_map:
37
- label_info = label_map[main_label]
38
- main_result = f"""
39
- <div style="
40
- background: linear-gradient(135deg, {label_info['color']}22, {label_info['color']}11);
41
- border: 2px solid {label_info['color']};
42
- border-radius: 15px;
43
- padding: 20px;
44
- text-align: center;
45
- margin: 10px 0;
46
- box-shadow: 0 4px 15px rgba(0,0,0,0.1);
47
- ">
48
- <div style="font-size: 48px; margin-bottom: 10px;">{label_info['emoji']}</div>
49
- <div style="font-size: 24px; font-weight: bold; color: {label_info['color']}; margin-bottom: 5px;">
50
- {label_info['name']}
51
- </div>
52
- <div style="font-size: 18px; color: #666;">
53
- Độ tin cậy: <strong>{round(main_score*100, 1)}%</strong>
54
- </div>
55
- </div>
56
- """
57
- else:
58
- main_result = f"Không xác định được nhãn: {main_label}"
59
-
60
- # Tạo biểu đồ phân phối điểm số
61
- if all_scores:
62
- labels = []
63
- scores = []
64
- colors = []
65
-
66
- for item in all_scores:
67
- label_key = item['label']
68
- if label_key in label_map:
69
- labels.append(label_map[label_key]['name'])
70
- scores.append(item['score'])
71
- colors.append(label_map[label_key]['color'])
72
-
73
- # Tạo biểu đồ thanh ngang
74
- fig = go.Figure(data=[
75
- go.Bar(
76
- y=labels,
77
- x=scores,
78
- orientation='h',
79
- marker_color=colors,
80
- text=[f"{s:.1%}" for s in scores],
81
- textposition='inside',
82
- textfont=dict(color='white', size=12, family='Arial Black')
83
- )
84
- ])
85
-
86
- fig.update_layout(
87
- title={
88
- 'text': 'Phân phối điểm số dự đoán',
89
- 'x': 0.5,
90
- 'font': {'size': 16, 'family': 'Arial', 'color': '#333'}
91
- },
92
- xaxis_title="Điểm số",
93
- yaxis_title="Loại bình luận",
94
- height=300,
95
- margin=dict(l=20, r=20, t=50, b=20),
96
- plot_bgcolor='rgba(0,0,0,0)',
97
- paper_bgcolor='rgba(0,0,0,0)',
98
- font=dict(family="Arial", size=12),
99
- xaxis=dict(
100
- showgrid=True,
101
- gridwidth=1,
102
- gridcolor='rgba(128,128,128,0.2)',
103
- range=[0, 1]
104
- ),
105
- yaxis=dict(
106
- showgrid=False
107
- )
108
- )
109
-
110
- # Chi tiết điểm số
111
- details = "<div style='margin-top: 15px;'>"
112
- details += "<h4 style='color: #333; margin-bottom: 10px;'>📊 Chi tiết điểm số:</h4>"
113
- for item in sorted(all_scores, key=lambda x: x['score'], reverse=True):
114
- label_key = item['label']
115
- if label_key in label_map:
116
- info = label_map[label_key]
117
- percentage = item['score'] * 100
118
- bar_width = int(item['score'] * 100)
119
- details += f"""
120
- <div style="margin-bottom: 8px;">
121
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 3px;">
122
- <span style="font-weight: 500;">{info['emoji']} {info['name']}</span>
123
- <span style="font-weight: bold; color: {info['color']};">{percentage:.1f}%</span>
124
- </div>
125
- <div style="background: #f0f0f0; border-radius: 10px; height: 8px; overflow: hidden;">
126
- <div style="background: {info['color']}; height: 100%; width: {bar_width}%; border-radius: 10px;"></div>
127
- </div>
128
- </div>
129
- """
130
- details += "</div>"
131
-
132
- return main_result, fig, details
133
-
134
- return main_result, None, None
135
-
136
- # Custom CSS cho giao diện
137
- custom_css = """
138
- /* Import Google Fonts */
139
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
140
-
141
- /* Global styles */
142
- * {
143
- font-family: 'Inter', sans-serif !important;
144
- }
145
-
146
- /* Header styling */
147
- .gradio-container {
148
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
149
- min-height: 100vh;
150
- }
151
-
152
- /* Main container */
153
- #main_container {
154
- background: white;
155
- border-radius: 20px;
156
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
157
- margin: 20px;
158
- overflow: hidden;
159
- }
160
-
161
- /* Title area */
162
- .app-title {
163
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
164
- color: white;
165
- padding: 30px;
166
- text-align: center;
167
- margin: -20px -20px 30px -20px;
168
- }
169
-
170
- .app-title h1 {
171
- font-size: 2.5rem;
172
- font-weight: 700;
173
- margin-bottom: 10px;
174
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
175
- }
176
-
177
- .app-title p {
178
- font-size: 1.1rem;
179
- opacity: 0.9;
180
- font-weight: 300;
181
- }
182
-
183
- /* Input styling */
184
- .input-container textarea {
185
- border: 2px solid #e2e8f0 !important;
186
- border-radius: 12px !important;
187
- padding: 15px !important;
188
- font-size: 16px !important;
189
- transition: all 0.3s ease !important;
190
- resize: vertical !important;
191
- }
192
-
193
- .input-container textarea:focus {
194
- border-color: #667eea !important;
195
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
196
- outline: none !important;
197
- }
198
-
199
- /* Button styling */
200
- button.primary {
201
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
202
- border: none !important;
203
- border-radius: 12px !important;
204
- padding: 12px 30px !important;
205
- font-weight: 600 !important;
206
- text-transform: uppercase !important;
207
- letter-spacing: 0.5px !important;
208
- transition: all 0.3s ease !important;
209
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
210
- }
211
-
212
- button.primary:hover {
213
- transform: translateY(-2px) !important;
214
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4) !important;
215
- }
216
-
217
- /* Examples styling */
218
- .examples-container {
219
- background: #f8fafc;
220
- border-radius: 12px;
221
- padding: 20px;
222
- margin-top: 20px;
223
- }
224
-
225
- .examples-container h3 {
226
- color: #334155;
227
- margin-bottom: 15px;
228
- font-weight: 600;
229
- }
230
-
231
- /* Output styling */
232
- .output-container {
233
- background: #ffffff;
234
- border-radius: 12px;
235
- border: 1px solid #e2e8f0;
236
- margin-top: 20px;
237
- }
238
-
239
- /* Tab styling */
240
- .tab-nav button {
241
- border-radius: 8px 8px 0 0 !important;
242
- font-weight: 500 !important;
243
- }
244
-
245
- .tab-nav button.selected {
246
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
247
- color: white !important;
248
- }
249
-
250
- /* Footer */
251
- .footer {
252
- text-align: center;
253
- padding: 20px;
254
- color: #64748b;
255
- font-size: 14px;
256
- border-top: 1px solid #e2e8f0;
257
- margin-top: 30px;
258
- }
259
- """
260
-
261
- # Tạo giao diện Gradio
262
- with gr.Blocks(css=custom_css, title="Phân loại bình luận tiếng Việt - PhoBERT", theme=gr.themes.Soft()) as demo:
263
- # Header
264
- gr.HTML("""
265
- <div class="app-title">
266
- <h1>🧠 Phân loại bình luận tiếng Việt</h1>
267
- <p>Sử dụng mô hình PhoBERT để phân tích cảm xúc và độc hại trong bình luận mạng xã hội</p>
268
- </div>
269
- """)
270
-
271
- with gr.Row():
272
- with gr.Column(scale=1):
273
- # Input section
274
- gr.HTML("<h3 style='color: #334155; font-weight: 600; margin-bottom: 15px;'>📝 Nhập bình luận cần phân tích</h3>")
275
-
276
- input_text = gr.Textbox(
277
- lines=4,
278
- placeholder="Nhập bình luận tiếng Việt để phân tích cảm xúc và độ độc hại...\n\nVí dụ: 'Sản phẩm này thật tuyệt vời, tôi rất hài lòng!'",
279
- label="",
280
- elem_classes=["input-container"]
281
- )
282
-
283
- submit_btn = gr.Button(
284
- "🔍 Phân tích bình luận",
285
- variant="primary",
286
- size="lg"
287
- )
288
-
289
- # Examples section
290
- gr.HTML("""
291
- <div class="examples-container">
292
- <h3>💡 dụ mẫu:</h3>
293
- <p style="color: #64748b; margin-bottom: 10px;">Nhấp vào các ví dụ bên dưới để thử nghiệm:</p>
294
- </div>
295
- """)
296
-
297
- gr.Examples(
298
- examples=[
299
- "Bạn làm tốt lắm, cảm ơn nhiều! Tôi rất hài lòng với sản phẩm này.",
300
- "Sản phẩm quá tệ, không đáng tiền. Chất lượng kém quá!",
301
- "Tôi không có ý kiến gì đặc biệt về vấn đề này.",
302
- "Mày bị điên à, nói chuyện như vậy mà cũng được?",
303
- "Dịch vụ khách hàng rất tốt, nhân viên nhiệt tình hỗ trợ.",
304
- "Giao hàng chậm quá, đã 1 tuần rồi mà chưa nhận được.",
305
- "Thông tin này khá hữu ích, cảm ơn bạn đã chia sẻ.",
306
- "Đồ rác, ai mua là ngu! Tiền bỏ ra sông bỏ ra bể."
307
- ],
308
- inputs=input_text
309
- )
310
-
311
- with gr.Column(scale=1):
312
- # Output section
313
- gr.HTML("<h3 style='color: #334155; font-weight: 600; margin-bottom: 15px;'>📊 Kết quả phân tích</h3>")
314
-
315
- with gr.Tabs():
316
- with gr.TabItem("🎯 Kết quả chính", elem_id="main_result_tab"):
317
- result_output = gr.HTML(
318
- value="<div style='text-align: center; padding: 40px; color: #64748b;'>Nhập bình luận và nhấn 'Phân tích' để xem kết quả</div>"
319
- )
320
-
321
- with gr.TabItem("📈 Biểu đồ phân phối", elem_id="chart_tab"):
322
- chart_output = gr.Plot()
323
-
324
- with gr.TabItem("📋 Chi tiết điểm số", elem_id="details_tab"):
325
- details_output = gr.HTML()
326
-
327
- # Event handlers
328
- submit_btn.click(
329
- fn=classify_comment,
330
- inputs=input_text,
331
- outputs=[result_output, chart_output, details_output]
332
- )
333
-
334
- input_text.submit(
335
- fn=classify_comment,
336
- inputs=input_text,
337
- outputs=[result_output, chart_output, details_output]
338
- )
339
-
340
- # Footer
341
- gr.HTML("""
342
- <div class="footer">
343
- <p>
344
- <strong>Mô hình:</strong> PhoBERT fine-tuned cho phân loại bình luận tiếng Việt<br>
345
- <strong>Các nhãn:</strong> Tích cực • Tiêu cực • Trung tính • Độc hại<br>
346
- <em>Được xây dựng với ❤️ sử dụng Transformers và Gradio</em>
347
- </p>
348
- </div>
349
- """)
350
-
351
- # Launch the app
352
- demo.launch(
353
- share=True,
354
- server_name="0.0.0.0",
355
- server_port=7860,
356
- show_error=True,
357
- quiet=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  )
 
1
+ import gradio as gr
2
+ from transformers import pipeline
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+
7
+ # Load mô hình từ Hugging Face Model Hub
8
+ pipe = pipeline("text-classification", model="vanhai123/phobert-vi-comment-4class", tokenizer="vanhai123/phobert-vi-comment-4class")
9
+
10
+ # Map nhãn lại cho dễ đọc với emoji và màu sắc
11
+ label_map = {
12
+ "LABEL_0": {"name": "Tích cực", "emoji": "😊", "color": "#10b981"},
13
+ "LABEL_1": {"name": "Tiêu cực", "emoji": "😞", "color": "#f59e0b"},
14
+ "LABEL_2": {"name": "Trung tính", "emoji": "😐", "color": "#6b7280"},
15
+ "LABEL_3": {"name": "Độc hại", "emoji": "😡", "color": "#ef4444"}
16
+ }
17
+
18
+ def classify_comment(comment):
19
+ if not comment.strip():
20
+ return "Vui lòng nhập bình luận để phân tích!", None, None
21
+
22
+ # Lấy tất cả kết quả dự đoán
23
+ results = pipe(comment)
24
+ if isinstance(results, list):
25
+ results = results[0] if results else {}
26
+
27
+ # Lấy kết quả chi tiết cho tất cả các nhãn
28
+ all_scores = pipe(comment, return_all_scores=True)
29
+ if isinstance(all_scores, list):
30
+ all_scores = all_scores[0] if all_scores else []
31
+
32
+ # Tạo kết quả chính
33
+ main_label = results.get('label', 'UNKNOWN')
34
+ main_score = results.get('score', 0)
35
+
36
+ if main_label in label_map:
37
+ label_info = label_map[main_label]
38
+ main_result = f"""
39
+ <div style="
40
+ background: linear-gradient(135deg, {label_info['color']}15, {label_info['color']}08);
41
+ border: 1px solid {label_info['color']}40;
42
+ border-radius: 16px;
43
+ padding: 24px;
44
+ text-align: center;
45
+ margin: 16px 0;
46
+ backdrop-filter: blur(10px);
47
+ box-shadow: 0 8px 32px rgba(0,0,0,0.08);
48
+ ">
49
+ <div style="font-size: 52px; margin-bottom: 12px; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));">{label_info['emoji']}</div>
50
+ <div style="font-size: 26px; font-weight: 700; color: {label_info['color']}; margin-bottom: 8px; letter-spacing: -0.025em;">
51
+ {label_info['name']}
52
+ </div>
53
+ <div style="font-size: 16px; color: #6b7280; font-weight: 500;">
54
+ Độ tin cậy: <span style="color: {label_info['color']}; font-weight: 700;">{round(main_score*100, 1)}%</span>
55
+ </div>
56
+ </div>
57
+ """
58
+ else:
59
+ main_result = f"Không xác định được nhãn: {main_label}"
60
+
61
+ # Tạo biểu đồ phân phối điểm số
62
+ if all_scores:
63
+ labels = []
64
+ scores = []
65
+ colors = []
66
+
67
+ for item in all_scores:
68
+ label_key = item['label']
69
+ if label_key in label_map:
70
+ labels.append(label_map[label_key]['name'])
71
+ scores.append(item['score'])
72
+ colors.append(label_map[label_key]['color'])
73
+
74
+ # Tạo biểu đồ thanh ngang với gradient
75
+ fig = go.Figure(data=[
76
+ go.Bar(
77
+ y=labels,
78
+ x=scores,
79
+ orientation='h',
80
+ marker=dict(
81
+ color=colors,
82
+ line=dict(width=0),
83
+ opacity=0.8
84
+ ),
85
+ text=[f"{s:.1%}" for s in scores],
86
+ textposition='inside',
87
+ textfont=dict(color='white', size=14, family='Inter', weight='bold'),
88
+ hovertemplate='<b>%{y}</b><br>Điểm số: %{x:.1%}<extra></extra>'
89
+ )
90
+ ])
91
+
92
+ fig.update_layout(
93
+ title={
94
+ 'text': '📊 Phân phối điểm số dự đoán',
95
+ 'x': 0.5,
96
+ 'font': {'size': 18, 'family': 'Inter', 'color': '#1f2937', 'weight': 'bold'}
97
+ },
98
+ xaxis_title="Điểm số",
99
+ yaxis_title="",
100
+ height=280,
101
+ margin=dict(l=20, r=20, t=60, b=40),
102
+ plot_bgcolor='rgba(255,255,255,0)',
103
+ paper_bgcolor='rgba(255,255,255,0)',
104
+ font=dict(family="Inter", size=13, color='#4b5563'),
105
+ xaxis=dict(
106
+ showgrid=True,
107
+ gridwidth=1,
108
+ gridcolor='rgba(156,163,175,0.2)',
109
+ range=[0, 1],
110
+ tickformat=".0%"
111
+ ),
112
+ yaxis=dict(
113
+ showgrid=False,
114
+ tickfont=dict(size=14, weight='500')
115
+ )
116
+ )
117
+
118
+ # Chi tiết điểm số
119
+ details = "<div style='margin-top: 20px; padding: 0 4px;'>"
120
+ details += "<h4 style='color: #1f2937; margin-bottom: 16px; font-size: 18px; font-weight: 700; display: flex; align-items: center;'><span style='margin-right: 8px;'>📈</span> Chi tiết điểm số</h4>"
121
+ for item in sorted(all_scores, key=lambda x: x['score'], reverse=True):
122
+ label_key = item['label']
123
+ if label_key in label_map:
124
+ info = label_map[label_key]
125
+ percentage = item['score'] * 100
126
+ bar_width = int(item['score'] * 100)
127
+ details += f"""
128
+ <div style="margin-bottom: 16px; padding: 16px; background: #f9fafb; border-radius: 12px; border: 1px solid #e5e7eb;">
129
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
130
+ <span style="font-weight: 600; color: #374151; font-size: 15px;">{info['emoji']} {info['name']}</span>
131
+ <span style="font-weight: 700; color: {info['color']}; font-size: 16px;">{percentage:.1f}%</span>
132
+ </div>
133
+ <div style="background: #e5e7eb; border-radius: 8px; height: 10px; overflow: hidden;">
134
+ <div style="background: linear-gradient(90deg, {info['color']}, {info['color']}cc); height: 100%; width: {bar_width}%; border-radius: 8px; transition: all 0.3s ease;"></div>
135
+ </div>
136
+ </div>
137
+ """
138
+ details += "</div>"
139
+
140
+ return main_result, fig, details
141
+
142
+ return main_result, None, None
143
+
144
+ # Custom CSS cho giao diện hiện đại
145
+ custom_css = """
146
+ /* Import Google Fonts */
147
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
148
+
149
+ /* Global styles */
150
+ * {
151
+ font-family: 'Inter', sans-serif !important;
152
+ }
153
+
154
+ /* Main container với dark theme */
155
+ .gradio-container {
156
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%) !important;
157
+ min-height: 100vh;
158
+ }
159
+
160
+ #main_container {
161
+ background: rgba(255, 255, 255, 0.95) !important;
162
+ backdrop-filter: blur(20px) !important;
163
+ border-radius: 24px !important;
164
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important;
165
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
166
+ margin: 20px !important;
167
+ overflow: hidden !important;
168
+ }
169
+
170
+ /* Header với gradient hiện đại */
171
+ .app-title {
172
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%) !important;
173
+ color: white !important;
174
+ padding: 40px 30px !important;
175
+ text-align: center !important;
176
+ margin: -20px -20px 40px -20px !important;
177
+ position: relative !important;
178
+ }
179
+
180
+ .app-title::before {
181
+ content: '';
182
+ position: absolute;
183
+ top: 0;
184
+ left: 0;
185
+ right: 0;
186
+ bottom: 0;
187
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="white" opacity="0.1"/><circle cx="75" cy="75" r="1" fill="white" opacity="0.1"/><circle cx="50" cy="10" r="0.5" fill="white" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>') !important;
188
+ opacity: 0.1 !important;
189
+ }
190
+
191
+ .app-title h1 {
192
+ font-size: 2.75rem !important;
193
+ font-weight: 800 !important;
194
+ margin-bottom: 12px !important;
195
+ text-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
196
+ letter-spacing: -0.025em !important;
197
+ position: relative !important;
198
+ z-index: 1 !important;
199
+ }
200
+
201
+ .app-title p {
202
+ font-size: 1.125rem !important;
203
+ opacity: 0.9 !important;
204
+ font-weight: 400 !important;
205
+ position: relative !important;
206
+ z-index: 1 !important;
207
+ }
208
+
209
+ /* Input styling hiện đại */
210
+ .input-container textarea {
211
+ border: 2px solid #e5e7eb !important;
212
+ border-radius: 16px !important;
213
+ padding: 20px !important;
214
+ font-size: 16px !important;
215
+ line-height: 1.6 !important;
216
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
217
+ resize: vertical !important;
218
+ background: #fafafa !important;
219
+ font-weight: 400 !important;
220
+ }
221
+
222
+ .input-container textarea:focus {
223
+ border-color: #6366f1 !important;
224
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1) !important;
225
+ outline: none !important;
226
+ background: white !important;
227
+ transform: translateY(-1px) !important;
228
+ }
229
+
230
+ /* Button styling với gradient và animation */
231
+ button.primary {
232
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
233
+ border: none !important;
234
+ border-radius: 14px !important;
235
+ padding: 16px 32px !important;
236
+ font-weight: 700 !important;
237
+ font-size: 15px !important;
238
+ letter-spacing: 0.025em !important;
239
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
240
+ box-shadow: 0 8px 25px rgba(99, 102, 241, 0.35) !important;
241
+ position: relative !important;
242
+ overflow: hidden !important;
243
+ }
244
+
245
+ button.primary::before {
246
+ content: '';
247
+ position: absolute;
248
+ top: 0;
249
+ left: -100%;
250
+ width: 100%;
251
+ height: 100%;
252
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
253
+ transition: left 0.5s ease;
254
+ }
255
+
256
+ button.primary:hover {
257
+ transform: translateY(-3px) !important;
258
+ box-shadow: 0 12px 35px rgba(99, 102, 241, 0.4) !important;
259
+ }
260
+
261
+ button.primary:hover::before {
262
+ left: 100%;
263
+ }
264
+
265
+ /* Section headers */
266
+ h3 {
267
+ color: #1f2937 !important;
268
+ font-weight: 700 !important;
269
+ font-size: 18px !important;
270
+ margin-bottom: 16px !important;
271
+ display: flex !important;
272
+ align-items: center !important;
273
+ }
274
+
275
+ /* Examples styling */
276
+ .examples-container {
277
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important;
278
+ border: 1px solid #e2e8f0 !important;
279
+ border-radius: 16px !important;
280
+ padding: 24px !important;
281
+ margin-top: 24px !important;
282
+ }
283
+
284
+ .examples-container h3 {
285
+ color: #334155 !important;
286
+ margin-bottom: 12px !important;
287
+ font-weight: 700 !important;
288
+ font-size: 16px !important;
289
+ }
290
+
291
+ .examples-container p {
292
+ color: #64748b !important;
293
+ font-size: 14px !important;
294
+ margin-bottom: 16px !important;
295
+ }
296
+
297
+ /* Examples buttons */
298
+ .examples-container button {
299
+ background: white !important;
300
+ border: 1px solid #e2e8f0 !important;
301
+ border-radius: 10px !important;
302
+ padding: 12px 16px !important;
303
+ margin: 4px !important;
304
+ font-size: 13px !important;
305
+ color: #475569 !important;
306
+ transition: all 0.2s ease !important;
307
+ font-weight: 500 !important;
308
+ }
309
+
310
+ .examples-container button:hover {
311
+ border-color: #6366f1 !important;
312
+ color: #6366f1 !important;
313
+ background: #f8faff !important;
314
+ transform: translateY(-1px) !important;
315
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.15) !important;
316
+ }
317
+
318
+ /* Tabs styling */
319
+ .tab-nav {
320
+ background: #f8fafc !important;
321
+ border-radius: 12px !important;
322
+ padding: 4px !important;
323
+ margin-bottom: 20px !important;
324
+ }
325
+
326
+ .tab-nav button {
327
+ border-radius: 8px !important;
328
+ font-weight: 600 !important;
329
+ font-size: 14px !important;
330
+ padding: 12px 20px !important;
331
+ transition: all 0.2s ease !important;
332
+ border: none !important;
333
+ background: transparent !important;
334
+ color: #64748b !important;
335
+ }
336
+
337
+ .tab-nav button.selected {
338
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%) !important;
339
+ color: white !important;
340
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3) !important;
341
+ }
342
+
343
+ /* Output containers */
344
+ .output-container {
345
+ background: white !important;
346
+ border-radius: 16px !important;
347
+ border: 1px solid #e5e7eb !important;
348
+ margin-top: 20px !important;
349
+ padding: 20px !important;
350
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
351
+ }
352
+
353
+ /* Footer */
354
+ .footer {
355
+ text-align: center !important;
356
+ padding: 32px 20px !important;
357
+ color: #6b7280 !important;
358
+ font-size: 14px !important;
359
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%) !important;
360
+ border-top: 1px solid #e5e7eb !important;
361
+ margin: 40px -20px -20px -20px !important;
362
+ line-height: 1.6 !important;
363
+ }
364
+
365
+ .footer strong {
366
+ color: #374151 !important;
367
+ font-weight: 600 !important;
368
+ }
369
+
370
+ /* Responsive */
371
+ @media (max-width: 768px) {
372
+ .app-title h1 {
373
+ font-size: 2rem !important;
374
+ }
375
+
376
+ .app-title p {
377
+ font-size: 1rem !important;
378
+ }
379
+
380
+ #main_container {
381
+ margin: 10px !important;
382
+ border-radius: 20px !important;
383
+ }
384
+
385
+ .input-container textarea {
386
+ padding: 16px !important;
387
+ font-size: 15px !important;
388
+ }
389
+
390
+ button.primary {
391
+ padding: 14px 24px !important;
392
+ font-size: 14px !important;
393
+ }
394
+ }
395
+
396
+ /* Loading animation */
397
+ @keyframes pulse {
398
+ 0%, 100% { opacity: 1; }
399
+ 50% { opacity: 0.5; }
400
+ }
401
+
402
+ .loading {
403
+ animation: pulse 2s infinite;
404
+ }
405
+
406
+ /* Scrollbar styling */
407
+ ::-webkit-scrollbar {
408
+ width: 8px;
409
+ }
410
+
411
+ ::-webkit-scrollbar-track {
412
+ background: #f1f5f9;
413
+ border-radius: 4px;
414
+ }
415
+
416
+ ::-webkit-scrollbar-thumb {
417
+ background: linear-gradient(135deg, #6366f1, #8b5cf6);
418
+ border-radius: 4px;
419
+ }
420
+
421
+ ::-webkit-scrollbar-thumb:hover {
422
+ background: linear-gradient(135deg, #4f46e5, #7c3aed);
423
+ }
424
+ """
425
+
426
+ # Tạo giao diện Gradio
427
+ with gr.Blocks(css=custom_css, title="Phân loại bình luận tiếng Việt - PhoBERT", theme=gr.themes.Soft()) as demo:
428
+ # Header
429
+ gr.HTML("""
430
+ <div class="app-title">
431
+ <h1>🤖 Phân loại bình luận tiếng Việt</h1>
432
+ <p>Phân tích cảm xúc và độc hại trong bình luận với công nghệ PhoBERT</p>
433
+ </div>
434
+ """)
435
+
436
+ with gr.Row():
437
+ with gr.Column(scale=1):
438
+ # Input section
439
+ gr.HTML("<h3>✍️ Nhập bình luận để phân tích</h3>")
440
+
441
+ input_text = gr.Textbox(
442
+ lines=4,
443
+ placeholder="Nhập bình luận tiếng Việt tại đây...\n\nVí dụ: 'Sản phẩm này thật tuyệt vời, tôi rất hài lòng!'",
444
+ label="",
445
+ elem_classes=["input-container"]
446
+ )
447
+
448
+ submit_btn = gr.Button(
449
+ "🔍 Phân tích ngay",
450
+ variant="primary",
451
+ size="lg"
452
+ )
453
+
454
+ # Examples section
455
+ gr.HTML("""
456
+ <div class="examples-container">
457
+ <h3>💡 Mẫu bình luận để thử nghiệm</h3>
458
+ <p>Chọn một ví dụ bên dưới để xem cách hoạt động:</p>
459
+ </div>
460
+ """)
461
+
462
+ gr.Examples(
463
+ examples=[
464
+ "Bạn làm tốt lắm, cảm ơn nhiều! Tôi rất hài lòng với sản phẩm này.",
465
+ "Sản phẩm quá tệ, không đáng tiền. Chất lượng kém quá!",
466
+ "Tôi không có ý kiến gì đặc biệt về vấn đề này.",
467
+ "Mày bị điên à, nói chuyện như vậy mà cũng được?",
468
+ "Dịch vụ khách hàng rất tốt, nhân viên nhiệt tình hỗ trợ.",
469
+ "Giao hàng chậm quá, đã 1 tuần rồi mà chưa nhận được.",
470
+ "Thông tin này khá hữu ích, cảm ơn bạn đã chia sẻ.",
471
+ "Đồ rác, ai mua là ngu! Tiền bỏ ra sông bỏ ra bể."
472
+ ],
473
+ inputs=input_text
474
+ )
475
+
476
+ with gr.Column(scale=1):
477
+ # Output section
478
+ gr.HTML("<h3>📊 Kết quả phân tích</h3>")
479
+
480
+ with gr.Tabs():
481
+ with gr.TabItem("🎯 Kết quả chính", elem_id="main_result_tab"):
482
+ result_output = gr.HTML(
483
+ value="<div style='text-align: center; padding: 60px 20px; color: #9ca3af; background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); border-radius: 16px; border: 2px dashed #d1d5db;'><div style='font-size: 48px; margin-bottom: 16px;'>🤔</div><div style='font-size: 18px; font-weight: 500;'>Nhập bình luận và nhấn 'Phân tích ngay' để xem kết quả</div></div>"
484
+ )
485
+
486
+ with gr.TabItem("📈 Biểu đồ", elem_id="chart_tab"):
487
+ chart_output = gr.Plot()
488
+
489
+ with gr.TabItem("📋 Chi tiết", elem_id="details_tab"):
490
+ details_output = gr.HTML()
491
+
492
+ # Event handlers
493
+ submit_btn.click(
494
+ fn=classify_comment,
495
+ inputs=input_text,
496
+ outputs=[result_output, chart_output, details_output]
497
+ )
498
+
499
+ input_text.submit(
500
+ fn=classify_comment,
501
+ inputs=input_text,
502
+ outputs=[result_output, chart_output, details_output]
503
+ )
504
+
505
+ # Footer
506
+ gr.HTML("""
507
+ <div class="footer">
508
+ <p>
509
+ <strong>🧠 Mô hình:</strong> PhoBERT fine-tuned cho phân loại bình luận tiếng Việt<br>
510
+ <strong>🏷️ Các nhãn:</strong> Tích cực • Tiêu cực • Trung tính • Độc hại<br>
511
+ <strong>⚡ Công nghệ:</strong> Transformers • Gradio • Plotly<br>
512
+ <em>Được phát triển bởi Ha Van Hai</em>
513
+ </p>
514
+ </div>
515
+ """)
516
+
517
+ # Launch the app
518
+ demo.launch(
519
+ share=True,
520
+ server_name="0.0.0.0",
521
+ server_port=7860,
522
+ show_error=True,
523
+ quiet=False
524
  )