vanhai123 commited on
Commit
cf8931d
·
verified ·
1 Parent(s): 0016b8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +402 -69
app.py CHANGED
@@ -49,7 +49,7 @@ def classify_comment(comment):
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>
@@ -57,7 +57,7 @@ def classify_comment(comment):
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 = []
@@ -70,7 +70,7 @@ def classify_comment(comment):
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,
@@ -79,7 +79,7 @@ def classify_comment(comment):
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
 
@@ -87,7 +87,7 @@ def classify_comment(comment):
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",
@@ -95,21 +95,23 @@ def classify_comment(comment):
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:
@@ -119,10 +121,10 @@ def classify_comment(comment):
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>
@@ -133,145 +135,476 @@ def classify_comment(comment):
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,
@@ -289,8 +622,8 @@ with gr.Blocks(css=custom_css, title="Phân loại bình luận tiếng Việt -
289
  # Examples section
290
  gr.HTML("""
291
  <div class="examples-container">
292
- <h3>💡 Ví 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
 
@@ -310,12 +643,12 @@ with gr.Blocks(css=custom_css, title="Phân loại bình luận tiếng Việt -
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"):
@@ -343,7 +676,7 @@ with gr.Blocks(css=custom_css, title="Phân loại bình luận tiếng Việt -
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 bởi Ha Van Hai</em>
347
  </p>
348
  </div>
349
  """)
 
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: var(--text-secondary);">
53
  Độ tin cậy: <strong>{round(main_score*100, 1)}%</strong>
54
  </div>
55
  </div>
 
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ố với theme tự động
61
  if all_scores:
62
  labels = []
63
  scores = []
 
70
  scores.append(item['score'])
71
  colors.append(label_map[label_key]['color'])
72
 
73
+ # Tạo biểu đồ thanh ngang với theme responsive
74
  fig = go.Figure(data=[
75
  go.Bar(
76
  y=labels,
 
79
  marker_color=colors,
80
  text=[f"{s:.1%}" for s in scores],
81
  textposition='inside',
82
+ textfont=dict(color='white', size=12, family='Inter')
83
  )
84
  ])
85
 
 
87
  title={
88
  'text': 'Phân phối điểm số dự đoán',
89
  'x': 0.5,
90
+ 'font': {'size': 16, 'family': 'Inter', 'color': 'var(--text-primary)'}
91
  },
92
  xaxis_title="Điểm số",
93
  yaxis_title="Loại bình luận",
 
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="Inter", size=12, color='var(--text-primary)'),
99
  xaxis=dict(
100
  showgrid=True,
101
  gridwidth=1,
102
+ gridcolor='var(--border-light)',
103
+ range=[0, 1],
104
+ color='var(--text-primary)'
105
  ),
106
  yaxis=dict(
107
+ showgrid=False,
108
+ color='var(--text-primary)'
109
  )
110
  )
111
 
112
+ # Chi tiết điểm số với theme responsive
113
  details = "<div style='margin-top: 15px;'>"
114
+ details += "<h4 style='color: var(--text-primary); margin-bottom: 10px;'>📊 Chi tiết điểm số:</h4>"
115
  for item in sorted(all_scores, key=lambda x: x['score'], reverse=True):
116
  label_key = item['label']
117
  if label_key in label_map:
 
121
  details += f"""
122
  <div style="margin-bottom: 8px;">
123
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 3px;">
124
+ <span style="font-weight: 500; color: var(--text-primary);">{info['emoji']} {info['name']}</span>
125
  <span style="font-weight: bold; color: {info['color']};">{percentage:.1f}%</span>
126
  </div>
127
+ <div style="background: var(--progress-bg); border-radius: 10px; height: 8px; overflow: hidden;">
128
  <div style="background: {info['color']}; height: 100%; width: {bar_width}%; border-radius: 10px;"></div>
129
  </div>
130
  </div>
 
135
 
136
  return main_result, None, None
137
 
138
+ # Custom CSS với hỗ trợ light/dark mode
139
  custom_css = """
140
  /* Import Google Fonts */
141
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
142
 
143
+ /* CSS Variables cho Light Mode */
144
+ :root {
145
+ --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
146
+ --secondary-gradient: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
147
+
148
+ /* Light mode colors */
149
+ --bg-primary: #ffffff;
150
+ --bg-secondary: #f8fafc;
151
+ --bg-tertiary: #f1f5f9;
152
+ --bg-header: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
153
+
154
+ --text-primary: #1e293b;
155
+ --text-secondary: #64748b;
156
+ --text-accent: #475569;
157
+ --text-on-primary: #ffffff;
158
+
159
+ --border-light: #e2e8f0;
160
+ --border-medium: #cbd5e1;
161
+ --border-strong: #94a3b8;
162
+
163
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
164
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
165
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
166
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
167
+
168
+ --progress-bg: #e2e8f0;
169
+ --input-bg: #ffffff;
170
+ --card-bg: #ffffff;
171
+ }
172
+
173
+ /* Dark Mode */
174
+ @media (prefers-color-scheme: dark) {
175
+ :root {
176
+ --bg-primary: #0f172a;
177
+ --bg-secondary: #1e293b;
178
+ --bg-tertiary: #334155;
179
+ --bg-header: linear-gradient(135deg, #4338ca 0%, #7c3aed 100%);
180
+
181
+ --text-primary: #f1f5f9;
182
+ --text-secondary: #94a3b8;
183
+ --text-accent: #cbd5e1;
184
+ --text-on-primary: #ffffff;
185
+
186
+ --border-light: #334155;
187
+ --border-medium: #475569;
188
+ --border-strong: #64748b;
189
+
190
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
191
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
192
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
193
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6);
194
+
195
+ --progress-bg: #334155;
196
+ --input-bg: #1e293b;
197
+ --card-bg: #1e293b;
198
+ }
199
+ }
200
+
201
+ /* Manual dark mode class override */
202
+ .dark {
203
+ --bg-primary: #0f172a;
204
+ --bg-secondary: #1e293b;
205
+ --bg-tertiary: #334155;
206
+ --bg-header: linear-gradient(135deg, #4338ca 0%, #7c3aed 100%);
207
+
208
+ --text-primary: #f1f5f9;
209
+ --text-secondary: #94a3b8;
210
+ --text-accent: #cbd5e1;
211
+ --text-on-primary: #ffffff;
212
+
213
+ --border-light: #334155;
214
+ --border-medium: #475569;
215
+ --border-strong: #64748b;
216
+
217
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
218
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
219
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
220
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.6);
221
+
222
+ --progress-bg: #334155;
223
+ --input-bg: #1e293b;
224
+ --card-bg: #1e293b;
225
+ }
226
+
227
  /* Global styles */
228
  * {
229
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
230
+ transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease !important;
231
  }
232
 
233
+ /* Main container styling */
234
  .gradio-container {
235
+ background: var(--bg-primary) !important;
236
  min-height: 100vh;
237
+ color: var(--text-primary) !important;
238
  }
239
 
240
+ /* Block container */
241
+ .block {
242
+ background: var(--card-bg) !important;
243
+ border: 1px solid var(--border-light) !important;
244
+ border-radius: 16px !important;
245
+ box-shadow: var(--shadow-md) !important;
 
246
  }
247
 
248
+ /* Header styling */
249
  .app-title {
250
+ background: var(--bg-header);
251
+ color: var(--text-on-primary);
252
+ padding: 40px 30px;
253
  text-align: center;
254
+ border-radius: 20px 20px 0 0;
255
  margin: -20px -20px 30px -20px;
256
+ position: relative;
257
+ overflow: hidden;
258
+ }
259
+
260
+ .app-title::before {
261
+ content: '';
262
+ position: absolute;
263
+ top: 0;
264
+ left: 0;
265
+ right: 0;
266
+ bottom: 0;
267
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 20"><defs><radialGradient id="a" cx="50%" cy="0%" r="100%"><stop offset="0%" stop-color="white" stop-opacity="0.1"/><stop offset="100%" stop-color="white" stop-opacity="0"/></radialGradient></defs><rect width="100" height="20" fill="url(%23a)"/></svg>');
268
+ pointer-events: none;
269
  }
270
 
271
  .app-title h1 {
272
+ font-size: clamp(1.8rem, 4vw, 2.5rem);
273
  font-weight: 700;
274
  margin-bottom: 10px;
275
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
276
+ position: relative;
277
+ z-index: 1;
278
  }
279
 
280
  .app-title p {
281
+ font-size: clamp(1rem, 2.5vw, 1.1rem);
282
+ opacity: 0.95;
283
+ font-weight: 400;
284
+ position: relative;
285
+ z-index: 1;
286
+ max-width: 600px;
287
+ margin: 0 auto;
288
+ line-height: 1.6;
289
+ }
290
+
291
+ /* Section headers */
292
+ h3 {
293
+ color: var(--text-primary) !important;
294
+ font-weight: 600 !important;
295
+ margin-bottom: 15px !important;
296
+ font-size: 1.25rem !important;
297
  }
298
 
299
  /* Input styling */
300
+ .input-container textarea,
301
+ textarea {
302
+ background: var(--input-bg) !important;
303
+ border: 2px solid var(--border-light) !important;
304
  border-radius: 12px !important;
305
+ padding: 16px !important;
306
  font-size: 16px !important;
307
+ color: var(--text-primary) !important;
308
  transition: all 0.3s ease !important;
309
  resize: vertical !important;
310
+ line-height: 1.5 !important;
311
  }
312
 
313
+ .input-container textarea:focus,
314
+ textarea:focus {
315
  border-color: #667eea !important;
316
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15) !important;
317
  outline: none !important;
318
+ background: var(--input-bg) !important;
319
+ }
320
+
321
+ .input-container textarea::placeholder,
322
+ textarea::placeholder {
323
+ color: var(--text-secondary) !important;
324
+ opacity: 0.8 !important;
325
  }
326
 
327
  /* Button styling */
328
+ button.primary,
329
+ .gr-button-primary {
330
+ background: var(--primary-gradient) !important;
331
  border: none !important;
332
  border-radius: 12px !important;
333
+ padding: 14px 32px !important;
334
  font-weight: 600 !important;
335
+ font-size: 16px !important;
336
+ color: var(--text-on-primary) !important;
337
+ text-transform: none !important;
338
+ letter-spacing: 0.025em !important;
339
  transition: all 0.3s ease !important;
340
+ box-shadow: var(--shadow-md) !important;
341
+ cursor: pointer !important;
342
  }
343
 
344
+ button.primary:hover,
345
+ .gr-button-primary:hover {
346
  transform: translateY(-2px) !important;
347
+ box-shadow: var(--shadow-lg) !important;
348
+ filter: brightness(1.05) !important;
349
+ }
350
+
351
+ button.primary:active,
352
+ .gr-button-primary:active {
353
+ transform: translateY(0) !important;
354
+ box-shadow: var(--shadow-md) !important;
355
  }
356
 
357
  /* Examples styling */
358
  .examples-container {
359
+ background: var(--bg-secondary);
360
+ border: 1px solid var(--border-light);
361
  border-radius: 12px;
362
  padding: 20px;
363
  margin-top: 20px;
364
  }
365
 
366
  .examples-container h3 {
367
+ color: var(--text-primary);
368
  margin-bottom: 15px;
369
  font-weight: 600;
370
+ font-size: 1.1rem;
371
  }
372
 
373
+ .examples-container p {
374
+ color: var(--text-secondary);
375
+ margin-bottom: 15px;
376
+ line-height: 1.5;
377
+ }
378
+
379
+ /* Example buttons */
380
+ .gr-examples .gr-button {
381
+ background: var(--bg-tertiary) !important;
382
+ border: 1px solid var(--border-light) !important;
383
+ border-radius: 8px !important;
384
+ color: var(--text-primary) !important;
385
+ padding: 12px 16px !important;
386
+ margin: 4px !important;
387
+ font-size: 14px !important;
388
+ line-height: 1.4 !important;
389
+ transition: all 0.2s ease !important;
390
+ text-align: left !important;
391
+ }
392
+
393
+ .gr-examples .gr-button:hover {
394
+ background: var(--bg-secondary) !important;
395
+ border-color: var(--border-medium) !important;
396
+ transform: translateY(-1px) !important;
397
+ box-shadow: var(--shadow-sm) !important;
398
  }
399
 
400
  /* Tab styling */
401
+ .tab-nav button,
402
+ .gr-tab-nav button {
403
  border-radius: 8px 8px 0 0 !important;
404
  font-weight: 500 !important;
405
+ background: var(--bg-tertiary) !important;
406
+ color: var(--text-secondary) !important;
407
+ border: 1px solid var(--border-light) !important;
408
+ border-bottom: none !important;
409
+ padding: 12px 20px !important;
410
+ transition: all 0.2s ease !important;
411
  }
412
 
413
+ .tab-nav button:hover,
414
+ .gr-tab-nav button:hover {
415
+ background: var(--bg-secondary) !important;
416
+ color: var(--text-primary) !important;
417
+ }
418
+
419
+ .tab-nav button.selected,
420
+ .gr-tab-nav button.selected {
421
+ background: var(--primary-gradient) !important;
422
+ color: var(--text-on-primary) !important;
423
+ border-color: #667eea !important;
424
+ }
425
+
426
+ /* Tab content */
427
+ .tabitem,
428
+ .gr-tabitem {
429
+ background: var(--card-bg) !important;
430
+ border: 1px solid var(--border-light) !important;
431
+ border-top: none !important;
432
+ border-radius: 0 0 12px 12px !important;
433
+ padding: 20px !important;
434
+ }
435
+
436
+ /* Output containers */
437
+ .output-container {
438
+ background: var(--card-bg);
439
+ border-radius: 12px;
440
+ border: 1px solid var(--border-light);
441
+ margin-top: 20px;
442
+ overflow: hidden;
443
+ }
444
+
445
+ /* Plot containers */
446
+ .plotly-graph-div {
447
+ background: var(--card-bg) !important;
448
+ border-radius: 8px !important;
449
  }
450
 
451
  /* Footer */
452
  .footer {
453
  text-align: center;
454
+ padding: 30px 20px;
455
+ color: var(--text-secondary);
456
  font-size: 14px;
457
+ border-top: 1px solid var(--border-light);
458
+ margin-top: 40px;
459
+ background: var(--bg-secondary);
460
+ border-radius: 0 0 16px 16px;
461
+ line-height: 1.6;
462
+ }
463
+
464
+ .footer strong {
465
+ color: var(--text-primary);
466
+ font-weight: 600;
467
+ }
468
+
469
+ .footer em {
470
+ color: var(--text-accent);
471
+ font-style: normal;
472
+ }
473
+
474
+ /* Responsive design */
475
+ @media (max-width: 768px) {
476
+ .app-title {
477
+ padding: 30px 20px;
478
+ }
479
+
480
+ .app-title h1 {
481
+ font-size: 2rem;
482
+ }
483
+
484
+ .app-title p {
485
+ font-size: 1rem;
486
+ }
487
+
488
+ button.primary,
489
+ .gr-button-primary {
490
+ padding: 12px 24px !important;
491
+ font-size: 15px !important;
492
+ }
493
+
494
+ .examples-container {
495
+ padding: 16px;
496
+ }
497
+
498
+ .footer {
499
+ padding: 20px 15px;
500
+ font-size: 13px;
501
+ }
502
+ }
503
+
504
+ /* Smooth transitions for theme changes */
505
+ * {
506
+ transition: background-color 0.3s ease,
507
+ color 0.3s ease,
508
+ border-color 0.3s ease,
509
+ box-shadow 0.3s ease !important;
510
+ }
511
+
512
+ /* Custom scrollbar */
513
+ ::-webkit-scrollbar {
514
+ width: 8px;
515
+ height: 8px;
516
+ }
517
+
518
+ ::-webkit-scrollbar-track {
519
+ background: var(--bg-secondary);
520
+ border-radius: 4px;
521
+ }
522
+
523
+ ::-webkit-scrollbar-thumb {
524
+ background: var(--border-strong);
525
+ border-radius: 4px;
526
+ }
527
+
528
+ ::-webkit-scrollbar-thumb:hover {
529
+ background: var(--text-secondary);
530
+ }
531
+
532
+ /* Loading states */
533
+ .loading {
534
+ opacity: 0.7;
535
+ pointer-events: none;
536
+ }
537
+
538
+ /* Focus indicators for accessibility */
539
+ button:focus-visible,
540
+ textarea:focus-visible {
541
+ outline: 2px solid #667eea !important;
542
+ outline-offset: 2px !important;
543
+ }
544
+
545
+ /* High contrast mode support */
546
+ @media (prefers-contrast: high) {
547
+ :root {
548
+ --border-light: var(--border-strong);
549
+ --text-secondary: var(--text-primary);
550
+ }
551
+ }
552
+
553
+ /* Reduced motion support */
554
+ @media (prefers-reduced-motion: reduce) {
555
+ * {
556
+ transition: none !important;
557
+ animation: none !important;
558
+ }
559
+ }
560
+ """
561
+
562
+ # Theme toggle script (JavaScript để detect và toggle theme)
563
+ theme_script = """
564
+ <script>
565
+ // Auto-detect system theme and apply appropriate class
566
+ function updateTheme() {
567
+ const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
568
+ const root = document.documentElement;
569
+
570
+ if (isDark) {
571
+ root.classList.add('dark');
572
+ } else {
573
+ root.classList.remove('dark');
574
+ }
575
+ }
576
+
577
+ // Initial theme setup
578
+ updateTheme();
579
+
580
+ // Listen for system theme changes
581
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateTheme);
582
+
583
+ // Manual theme toggle function (có thể được gọi từ button nếu cần)
584
+ window.toggleTheme = function() {
585
+ const root = document.documentElement;
586
+ root.classList.toggle('dark');
587
  }
588
+ </script>
589
  """
590
 
591
  # Tạo giao diện Gradio
592
  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:
593
+ # Theme detection script
594
+ gr.HTML(theme_script)
595
+
596
  # Header
597
  gr.HTML("""
598
  <div class="app-title">
599
  <h1>🧠 Phân loại bình luận tiếng Việt</h1>
600
+ <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 với giao diện thích ứng light/dark mode</p>
601
  </div>
602
  """)
603
 
604
  with gr.Row():
605
  with gr.Column(scale=1):
606
  # Input section
607
+ gr.HTML("<h3>📝 Nhập bình luận cần phân tích</h3>")
608
 
609
  input_text = gr.Textbox(
610
  lines=4,
 
622
  # Examples section
623
  gr.HTML("""
624
  <div class="examples-container">
625
+ <h3>💡 Ví dụ mẫu</h3>
626
+ <p>Nhấp vào các ví dụ bên dưới để thử nghiệm:</p>
627
  </div>
628
  """)
629
 
 
643
 
644
  with gr.Column(scale=1):
645
  # Output section
646
+ gr.HTML("<h3>📊 Kết quả phân tích</h3>")
647
 
648
  with gr.Tabs():
649
  with gr.TabItem("🎯 Kết quả chính", elem_id="main_result_tab"):
650
  result_output = gr.HTML(
651
+ value="<div style='text-align: center; padding: 40px; color: var(--text-secondary);'>Nhập bình luận và nhấn 'Phân tích' để xem kết quả</div>"
652
  )
653
 
654
  with gr.TabItem("📈 Biểu đồ phân phối", elem_id="chart_tab"):
 
676
  <p>
677
  <strong>Mô hình:</strong> PhoBERT fine-tuned cho phân loại bình luận tiếng Việt<br>
678
  <strong>Các nhãn:</strong> Tích cực • Tiêu cực • Trung tính • Độc hại<br>
679
+ <em>Được xây bởi Văn Hải</em>
680
  </p>
681
  </div>
682
  """)