VietKien commited on
Commit
b68cd35
·
verified ·
1 Parent(s): 4eda611

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +45 -188
app.py CHANGED
@@ -14,13 +14,13 @@ ocr_engine = None
14
 
15
  try:
16
  from paddleocr import PaddleOCR
17
- # Khởi tạo OCR engine (ẩn log để sạch console)
18
- ocr_engine = PaddleOCR(use_textline_orientation=True, lang='vi', show_log=False)
19
  HAS_OCR = True
20
  except Exception as e:
21
  print(f"⚠️ Cảnh báo: Không thể khởi tạo OCR. Lỗi: {e}")
22
 
23
- # --- 2. TỪ ĐIỂN NGÔN NGỮ (TEXT RESOURCES) ---
24
  TRANS = {
25
  "vi": {
26
  "app_name": "PRO DOCUMENT TOOLKIT",
@@ -28,23 +28,17 @@ TRANS = {
28
  "menu_pdf": "📄 Chuyển PDF sang Word",
29
  "menu_ocr": "👁️ Trích xuất chữ (OCR)",
30
  "lang_btn": "🇬🇧 English",
31
-
32
- # PDF Section
33
  "pdf_head": "Chuyển đổi Tài liệu",
34
  "pdf_sub": "Giữ nguyên định dạng gốc, hỗ trợ xử lý hàng loạt.",
35
  "pdf_label": "Thả file PDF vào đây hoặc click để tải lên",
36
  "pdf_btn": "🚀 BẮT ĐẦU CHUYỂN ĐỔI",
37
  "pdf_success": "Đã chuyển đổi thành công!",
38
-
39
- # OCR Section
40
  "ocr_head": "Trích xuất Văn bản (AI)",
41
  "ocr_sub": "Nhận diện tiếng Việt chính xác từ hình ảnh/scan.",
42
  "ocr_label": "Tải ảnh lên (PNG, JPG, BMP)",
43
  "ocr_btn": "🔍 PHÂN TÍCH ẢNH",
44
  "ocr_rs_text": "Văn bản nhận diện",
45
  "ocr_rs_img": "Vùng nhận diện",
46
-
47
- # Common
48
  "err_nofile": "Vui lòng tải file lên trước!",
49
  "err_ocr": "Chức năng OCR chưa được cài đặt đúng.",
50
  "footer": "© 2024 Developed by Chu Viet Kien. Powered by AI."
@@ -55,61 +49,46 @@ TRANS = {
55
  "menu_pdf": "📄 PDF to Word Converter",
56
  "menu_ocr": "👁️ Smart OCR Extraction",
57
  "lang_btn": "🇻🇳 Tiếng Việt",
58
-
59
- # PDF Section
60
  "pdf_head": "Document Converter",
61
  "pdf_sub": "Preserve original formatting, batch processing supported.",
62
  "pdf_label": "Drop PDF files here or click to upload",
63
  "pdf_btn": "🚀 START CONVERSION",
64
  "pdf_success": "Conversion completed successfully!",
65
-
66
- # OCR Section
67
  "ocr_head": "Text Extraction (AI)",
68
  "ocr_sub": "High-accuracy Vietnamese text recognition from images.",
69
  "ocr_label": "Upload Image (PNG, JPG, BMP)",
70
  "ocr_btn": "🔍 ANALYZE IMAGE",
71
  "ocr_rs_text": "Extracted Text",
72
  "ocr_rs_img": "Detected Zones",
73
-
74
- # Common
75
  "err_nofile": "Please upload a file first!",
76
  "err_ocr": "OCR engine is not properly installed.",
77
  "footer": "© 2024 Developed by Chu Viet Kien. Powered by AI."
78
  }
79
  }
80
 
81
- # --- 3. LOGIC XỬ LÝ (BACKEND) ---
82
 
83
  def convert_pdfs_to_word(pdf_files, lang_code, progress=gr.Progress()):
84
- """Xử lý chuyển đổi PDF -> Word"""
85
  T = TRANS[lang_code]
86
-
87
  if not pdf_files:
88
- gr.Warning(T["err_nofile"]) # Dùng thông báo nổi thay vì text box
89
  return None
90
 
91
  if not isinstance(pdf_files, list):
92
  pdf_files = [pdf_files]
93
 
94
  converted_files = []
95
-
96
  try:
97
- # Giả lập loading bar
98
  progress(0, desc="Starting...")
99
-
100
  for idx, pdf_file in enumerate(pdf_files):
101
  file_name = os.path.basename(pdf_file.name)
102
  progress((idx / len(pdf_files)), desc=f"Converting: {file_name}")
103
-
104
  docx_name = os.path.splitext(file_name)[0] + ".docx"
105
  cv = Converter(pdf_file.name)
106
  cv.convert(docx_name)
107
  cv.close()
108
  converted_files.append(docx_name)
109
 
110
- progress(1, desc="Finalizing...")
111
-
112
- # Thông báo thành công
113
  gr.Info(f"✅ {T['pdf_success']}")
114
 
115
  if len(converted_files) == 1:
@@ -119,27 +98,21 @@ def convert_pdfs_to_word(pdf_files, lang_code, progress=gr.Progress()):
119
  with zipfile.ZipFile(zip_name, 'w') as zf:
120
  for f in converted_files: zf.write(f)
121
  return zip_name
122
-
123
  except Exception as e:
124
  gr.Error(f"Error: {str(e)}")
125
  return None
126
 
127
  def run_ocr_func(image, lang_code):
128
- """Xử lý OCR"""
129
  T = TRANS[lang_code]
130
-
131
  if not HAS_OCR:
132
  gr.Error(T["err_ocr"])
133
  return None, None, None
134
-
135
  if image is None:
136
  gr.Warning(T["err_nofile"])
137
  return None, None, None
138
 
139
  try:
140
- gr.Info("⏳ AI is reading image...")
141
-
142
- # Chạy OCR
143
  result = ocr_engine.ocr(image, cls=True)
144
  txts, boxes = [], []
145
 
@@ -152,37 +125,30 @@ def run_ocr_func(image, lang_code):
152
 
153
  full_text = "\n".join(txts)
154
 
155
- # Vẽ box lên ảnh
156
  img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
157
  for box in boxes:
158
  pts = np.array(box, np.int32).reshape((-1, 1, 2))
159
  cv2.polylines(img_cv, [pts], True, (0, 0, 255), 2)
160
  final_img = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)
161
 
162
- # Lưu file txt
163
  txt_path = "ocr_result.txt"
164
  with open(txt_path, "w", encoding="utf-8") as f:
165
  f.write(full_text)
166
 
167
  return full_text, final_img, txt_path
168
-
169
  except Exception as e:
170
  gr.Error(f"OCR Error: {str(e)}")
171
  return None, None, None
172
 
173
- # --- 4. HÀM UI HELPER ---
174
  def change_lang(lang):
175
  new = "en" if lang == "vi" else "vi"
176
  T = TRANS[new]
177
  return (
178
  new, T["lang_btn"],
179
- # Sidebar labels
180
  gr.update(choices=[T["menu_pdf"], T["menu_ocr"]], label="MENU", value=T["menu_pdf"]),
181
- # Header text
182
  T["app_name"], T["app_desc"], T["footer"],
183
- # PDF labels
184
  T["pdf_head"], T["pdf_sub"], gr.update(label=T["pdf_label"]), T["pdf_btn"],
185
- # OCR labels
186
  T["ocr_head"], T["ocr_sub"], gr.update(label=T["ocr_label"]), T["ocr_btn"],
187
  gr.update(label=T["ocr_rs_text"]), gr.update(label=T["ocr_rs_img"])
188
  )
@@ -191,207 +157,93 @@ def toggle_view(menu_val, lang):
191
  is_pdf = (menu_val == TRANS["vi"]["menu_pdf"] or menu_val == TRANS["en"]["menu_pdf"])
192
  return (gr.update(visible=True), gr.update(visible=False)) if is_pdf else (gr.update(visible=False), gr.update(visible=True))
193
 
194
- # --- 5. STYLE & THEME (CSS CHUYÊN NGHIỆP) ---
195
-
196
- # Tùy chỉnh Theme có sẵn của Gradio
197
  professional_theme = gr.themes.Soft(
198
- primary_hue="blue", # Màu chủ đạo: Xanh chuyên nghiệp
199
- neutral_hue="slate", # Màu nền/chữ: Xám hiện đại
200
- radius_size="md", # Bo góc vừa phải
201
- font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
202
  ).set(
203
- body_background_fill="#f8fafc", # Màu nền tổng thể xám rất nhạt
204
- block_background_fill="#ffffff", # Các khối (block) màu trắng
205
  block_border_width="1px",
206
- block_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", # Đổ bóng nhẹ (Shadow)
207
- button_primary_background_fill="linear-gradient(90deg, #2563eb, #1d4ed8)", # Gradient xanh
208
- button_primary_background_fill_hover="linear-gradient(90deg, #1d4ed8, #1e40af)",
209
  button_primary_text_color="white",
210
  )
211
 
212
- # CSS tùy chỉnh thêm
213
  custom_css = """
214
- /* Ẩn footer mặc định của Gradio */
215
  footer {display: none !important;}
216
-
217
- /* Style cho Header */
218
- .app-header {
219
- text-align: center;
220
- margin-bottom: 30px;
221
- padding: 20px;
222
- }
223
- .app-title {
224
- font-size: 2rem;
225
- font-weight: 800;
226
- color: #1e293b;
227
- margin-bottom: 0.5rem;
228
- letter-spacing: -0.025em;
229
- }
230
- .app-desc {
231
- font-size: 1.1rem;
232
- color: #64748b;
233
- }
234
-
235
- /* Style cho Card tiêu đề từng tool */
236
- .tool-header {
237
- border-left: 4px solid #2563eb;
238
- padding-left: 15px;
239
- margin-bottom: 20px;
240
- }
241
- .tool-title {
242
- font-size: 1.5rem;
243
- font-weight: 700;
244
- color: #334155;
245
- }
246
- .tool-sub {
247
- color: #64748b;
248
- font-size: 0.95rem;
249
- }
250
-
251
- /* Tùy chỉnh nút bấm */
252
- button.primary-btn {
253
- font-weight: 600 !important;
254
- font-size: 1rem !important;
255
- padding: 12px 24px !important;
256
- transition: all 0.2s;
257
- }
258
- button.primary-btn:hover {
259
- transform: translateY(-1px);
260
- box-shadow: 0 10px 15px -3px rgba(37, 99, 235, 0.2);
261
- }
262
-
263
- /* Custom Footer riêng */
264
- .my-footer {
265
- text-align: center;
266
- margin-top: 40px;
267
- color: #94a3b8;
268
- font-size: 0.85rem;
269
- border-top: 1px solid #e2e8f0;
270
- padding-top: 20px;
271
- }
272
  """
273
 
274
- # --- 6. XÂY DỰNG GIAO DIỆN (UI LAYOUT) ---
275
-
276
- with gr.Blocks(theme=professional_theme, css=custom_css, title="Pro Toolkit") as demo:
277
 
278
- # State quản lý ngôn ngữ
279
  lang_state = gr.State("vi")
280
 
281
- # === SIDEBAR NAV ===
282
  with gr.Sidebar():
283
  gr.Markdown("### 🛠️ DASHBOARD")
284
-
285
  btn_lang = gr.Button(TRANS["vi"]["lang_btn"], size="sm", variant="secondary")
286
-
287
  radio_menu = gr.Radio(
288
  choices=[TRANS["vi"]["menu_pdf"], TRANS["vi"]["menu_ocr"]],
289
  value=TRANS["vi"]["menu_pdf"],
290
  label="MENU",
291
  interactive=True
292
  )
293
-
294
- gr.Markdown("---")
295
- gr.Markdown("""
296
- **System Status:** 🟢 Core: Online
297
- 🟢 AI Engine: Ready
298
- """)
299
 
300
- # === MAIN CONTENT ===
301
  with gr.Column(elem_classes="main-container"):
302
-
303
- # -- HEADER CHUNG --
304
  with gr.Column(elem_classes="app-header"):
305
  txt_title = gr.Markdown(f"<div class='app-title'>{TRANS['vi']['app_name']}</div>")
306
  txt_desc = gr.Markdown(f"<div class='app-desc'>{TRANS['vi']['app_desc']}</div>")
307
 
308
- # ==========================
309
- # TOOL 1: PDF TO WORD
310
- # ==========================
311
  with gr.Group(visible=True) as group_pdf:
312
- # Header Tool
313
  with gr.Column(elem_classes="tool-header"):
314
  t1_head = gr.Markdown(f"<div class='tool-title'>{TRANS['vi']['pdf_head']}</div>")
315
  t1_sub = gr.Markdown(f"<div class='tool-sub'>{TRANS['vi']['pdf_sub']}</div>")
316
 
317
- # Workspace (Chia cột Input / Output)
318
  with gr.Row(equal_height=True):
319
- # Cột trái: Upload
320
  with gr.Column(variant="panel"):
321
- in_pdf = gr.File(
322
- label=TRANS["vi"]["pdf_label"],
323
- file_types=[".pdf"],
324
- file_count="multiple",
325
- height=250
326
- )
327
- btn_pdf = gr.Button(
328
- TRANS["vi"]["pdf_btn"],
329
- variant="primary",
330
- elem_classes="primary-btn"
331
- )
332
-
333
- # Cột phải: Kết quả
334
  with gr.Column(variant="panel"):
335
- gr.Markdown("### 📂 Kết quả tải về")
336
  out_word = gr.File(label="Download", height=150)
337
- # Dùng Markdown để hiển thị hướng dẫn
338
- gr.Markdown("*File sẽ tự động nén ZIP nếu có nhiều tài liệu.*")
339
 
340
- # ==========================
341
  # TOOL 2: OCR
342
- # ==========================
343
  with gr.Group(visible=False) as group_ocr:
344
  with gr.Column(elem_classes="tool-header"):
345
  t2_head = gr.Markdown(f"<div class='tool-title'>{TRANS['vi']['ocr_head']}</div>")
346
  t2_sub = gr.Markdown(f"<div class='tool-sub'>{TRANS['vi']['ocr_sub']}</div>")
347
 
348
  with gr.Row():
349
- # Cột trái: Ảnh input + Nút
350
  with gr.Column(scale=4, variant="panel"):
351
- in_img = gr.Image(
352
- label=TRANS["vi"]["ocr_label"],
353
- type="numpy",
354
- height=400,
355
- sources=["upload", "clipboard"]
356
- )
357
- btn_ocr = gr.Button(
358
- TRANS["vi"]["ocr_btn"],
359
- variant="primary",
360
- elem_classes="primary-btn"
361
- )
362
-
363
- # Cột phải: Kết quả (Tab view)
364
  with gr.Column(scale=6, variant="panel"):
365
  with gr.Tabs():
366
  with gr.TabItem("📝 Text"):
367
- out_txt = gr.Textbox(
368
- label=TRANS["vi"]["ocr_rs_text"],
369
- lines=14,
370
- show_copy_button=True # Gradio mới đã hỗ trợ, nếu lỗi thì xóa dòng này
371
- )
372
  out_file = gr.File(label="Download .txt")
373
-
374
  with gr.TabItem("🖼️ Visualize"):
375
- out_img_viz = gr.Image(
376
- label=TRANS["vi"]["ocr_rs_img"],
377
- interactive=False
378
- )
379
 
380
- # -- FOOTER --
381
  txt_footer = gr.Markdown(f"<div class='my-footer'>{TRANS['vi']['footer']}</div>")
382
 
383
- # === SỰ KIỆN (EVENTS) ===
384
-
385
- # 1. Menu Navigation
386
  radio_menu.change(toggle_view, [radio_menu, lang_state], [group_pdf, group_ocr])
387
-
388
- # 2. Chức năng PDF
389
  btn_pdf.click(convert_pdfs_to_word, [in_pdf, lang_state], out_word)
390
-
391
- # 3. Chức năng OCR
392
  btn_ocr.click(run_ocr_func, [in_img, lang_state], [out_txt, out_img_viz, out_file])
393
-
394
- # 4. Đổi ngôn ngữ
395
  btn_lang.click(change_lang, [lang_state], [
396
  lang_state, btn_lang, radio_menu,
397
  txt_title, txt_desc, txt_footer,
@@ -399,7 +251,12 @@ with gr.Blocks(theme=professional_theme, css=custom_css, title="Pro Toolkit") as
399
  t2_head, t2_sub, in_img, btn_ocr, out_txt, out_img_viz
400
  ])
401
 
402
- # Chạy App
403
  if __name__ == "__main__":
404
- demo.queue() # Bật hàng đợi để xử lý mượt mà
405
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
14
 
15
  try:
16
  from paddleocr import PaddleOCR
17
+ # FIX LỖI 1: Bỏ tham số 'show_log' gây lỗi
18
+ ocr_engine = PaddleOCR(use_textline_orientation=True, lang='vi')
19
  HAS_OCR = True
20
  except Exception as e:
21
  print(f"⚠️ Cảnh báo: Không thể khởi tạo OCR. Lỗi: {e}")
22
 
23
+ # --- 2. TỪ ĐIỂN NGÔN NGỮ ---
24
  TRANS = {
25
  "vi": {
26
  "app_name": "PRO DOCUMENT TOOLKIT",
 
28
  "menu_pdf": "📄 Chuyển PDF sang Word",
29
  "menu_ocr": "👁️ Trích xuất chữ (OCR)",
30
  "lang_btn": "🇬🇧 English",
 
 
31
  "pdf_head": "Chuyển đổi Tài liệu",
32
  "pdf_sub": "Giữ nguyên định dạng gốc, hỗ trợ xử lý hàng loạt.",
33
  "pdf_label": "Thả file PDF vào đây hoặc click để tải lên",
34
  "pdf_btn": "🚀 BẮT ĐẦU CHUYỂN ĐỔI",
35
  "pdf_success": "Đã chuyển đổi thành công!",
 
 
36
  "ocr_head": "Trích xuất Văn bản (AI)",
37
  "ocr_sub": "Nhận diện tiếng Việt chính xác từ hình ảnh/scan.",
38
  "ocr_label": "Tải ảnh lên (PNG, JPG, BMP)",
39
  "ocr_btn": "🔍 PHÂN TÍCH ẢNH",
40
  "ocr_rs_text": "Văn bản nhận diện",
41
  "ocr_rs_img": "Vùng nhận diện",
 
 
42
  "err_nofile": "Vui lòng tải file lên trước!",
43
  "err_ocr": "Chức năng OCR chưa được cài đặt đúng.",
44
  "footer": "© 2024 Developed by Chu Viet Kien. Powered by AI."
 
49
  "menu_pdf": "📄 PDF to Word Converter",
50
  "menu_ocr": "👁️ Smart OCR Extraction",
51
  "lang_btn": "🇻🇳 Tiếng Việt",
 
 
52
  "pdf_head": "Document Converter",
53
  "pdf_sub": "Preserve original formatting, batch processing supported.",
54
  "pdf_label": "Drop PDF files here or click to upload",
55
  "pdf_btn": "🚀 START CONVERSION",
56
  "pdf_success": "Conversion completed successfully!",
 
 
57
  "ocr_head": "Text Extraction (AI)",
58
  "ocr_sub": "High-accuracy Vietnamese text recognition from images.",
59
  "ocr_label": "Upload Image (PNG, JPG, BMP)",
60
  "ocr_btn": "🔍 ANALYZE IMAGE",
61
  "ocr_rs_text": "Extracted Text",
62
  "ocr_rs_img": "Detected Zones",
 
 
63
  "err_nofile": "Please upload a file first!",
64
  "err_ocr": "OCR engine is not properly installed.",
65
  "footer": "© 2024 Developed by Chu Viet Kien. Powered by AI."
66
  }
67
  }
68
 
69
+ # --- 3. LOGIC XỬ LÝ ---
70
 
71
  def convert_pdfs_to_word(pdf_files, lang_code, progress=gr.Progress()):
 
72
  T = TRANS[lang_code]
 
73
  if not pdf_files:
74
+ gr.Warning(T["err_nofile"])
75
  return None
76
 
77
  if not isinstance(pdf_files, list):
78
  pdf_files = [pdf_files]
79
 
80
  converted_files = []
 
81
  try:
 
82
  progress(0, desc="Starting...")
 
83
  for idx, pdf_file in enumerate(pdf_files):
84
  file_name = os.path.basename(pdf_file.name)
85
  progress((idx / len(pdf_files)), desc=f"Converting: {file_name}")
 
86
  docx_name = os.path.splitext(file_name)[0] + ".docx"
87
  cv = Converter(pdf_file.name)
88
  cv.convert(docx_name)
89
  cv.close()
90
  converted_files.append(docx_name)
91
 
 
 
 
92
  gr.Info(f"✅ {T['pdf_success']}")
93
 
94
  if len(converted_files) == 1:
 
98
  with zipfile.ZipFile(zip_name, 'w') as zf:
99
  for f in converted_files: zf.write(f)
100
  return zip_name
 
101
  except Exception as e:
102
  gr.Error(f"Error: {str(e)}")
103
  return None
104
 
105
  def run_ocr_func(image, lang_code):
 
106
  T = TRANS[lang_code]
 
107
  if not HAS_OCR:
108
  gr.Error(T["err_ocr"])
109
  return None, None, None
 
110
  if image is None:
111
  gr.Warning(T["err_nofile"])
112
  return None, None, None
113
 
114
  try:
115
+ gr.Info("⏳ AI is processing...")
 
 
116
  result = ocr_engine.ocr(image, cls=True)
117
  txts, boxes = [], []
118
 
 
125
 
126
  full_text = "\n".join(txts)
127
 
 
128
  img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
129
  for box in boxes:
130
  pts = np.array(box, np.int32).reshape((-1, 1, 2))
131
  cv2.polylines(img_cv, [pts], True, (0, 0, 255), 2)
132
  final_img = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)
133
 
 
134
  txt_path = "ocr_result.txt"
135
  with open(txt_path, "w", encoding="utf-8") as f:
136
  f.write(full_text)
137
 
138
  return full_text, final_img, txt_path
 
139
  except Exception as e:
140
  gr.Error(f"OCR Error: {str(e)}")
141
  return None, None, None
142
 
143
+ # --- 4. UI HELPERS ---
144
  def change_lang(lang):
145
  new = "en" if lang == "vi" else "vi"
146
  T = TRANS[new]
147
  return (
148
  new, T["lang_btn"],
 
149
  gr.update(choices=[T["menu_pdf"], T["menu_ocr"]], label="MENU", value=T["menu_pdf"]),
 
150
  T["app_name"], T["app_desc"], T["footer"],
 
151
  T["pdf_head"], T["pdf_sub"], gr.update(label=T["pdf_label"]), T["pdf_btn"],
 
152
  T["ocr_head"], T["ocr_sub"], gr.update(label=T["ocr_label"]), T["ocr_btn"],
153
  gr.update(label=T["ocr_rs_text"]), gr.update(label=T["ocr_rs_img"])
154
  )
 
157
  is_pdf = (menu_val == TRANS["vi"]["menu_pdf"] or menu_val == TRANS["en"]["menu_pdf"])
158
  return (gr.update(visible=True), gr.update(visible=False)) if is_pdf else (gr.update(visible=False), gr.update(visible=True))
159
 
160
+ # --- 5. STYLE & THEME ---
 
 
161
  professional_theme = gr.themes.Soft(
162
+ primary_hue="blue",
163
+ neutral_hue="slate",
164
+ radius_size="md",
 
165
  ).set(
166
+ body_background_fill="#f8fafc",
167
+ block_background_fill="#ffffff",
168
  block_border_width="1px",
169
+ block_shadow="0 4px 6px -1px rgba(0, 0, 0, 0.1)",
170
+ button_primary_background_fill="linear-gradient(90deg, #2563eb, #1d4ed8)",
 
171
  button_primary_text_color="white",
172
  )
173
 
 
174
  custom_css = """
 
175
  footer {display: none !important;}
176
+ .app-header {text-align: center; margin-bottom: 30px; padding: 20px;}
177
+ .app-title {font-size: 2rem; font-weight: 800; color: #1e293b; margin-bottom: 0.5rem;}
178
+ .app-desc {font-size: 1.1rem; color: #64748b;}
179
+ .tool-header {border-left: 4px solid #2563eb; padding-left: 15px; margin-bottom: 20px;}
180
+ .tool-title {font-size: 1.5rem; font-weight: 700; color: #334155;}
181
+ .tool-sub {color: #64748b; font-size: 0.95rem;}
182
+ button.primary-btn {font-weight: 600 !important; padding: 12px 24px !important;}
183
+ .my-footer {text-align: center; margin-top: 40px; color: #94a3b8; font-size: 0.85rem; border-top: 1px solid #e2e8f0; padding-top: 20px;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  """
185
 
186
+ # --- 6. GIAO DIỆN ---
187
+ # Lưu ý: Không truyền theme/css vào đây nữa để tránh Warning
188
+ with gr.Blocks(title="Pro Toolkit") as demo:
189
 
 
190
  lang_state = gr.State("vi")
191
 
 
192
  with gr.Sidebar():
193
  gr.Markdown("### 🛠️ DASHBOARD")
 
194
  btn_lang = gr.Button(TRANS["vi"]["lang_btn"], size="sm", variant="secondary")
 
195
  radio_menu = gr.Radio(
196
  choices=[TRANS["vi"]["menu_pdf"], TRANS["vi"]["menu_ocr"]],
197
  value=TRANS["vi"]["menu_pdf"],
198
  label="MENU",
199
  interactive=True
200
  )
201
+ gr.Markdown("---\n**System Status:** 🟢 Online")
 
 
 
 
 
202
 
 
203
  with gr.Column(elem_classes="main-container"):
 
 
204
  with gr.Column(elem_classes="app-header"):
205
  txt_title = gr.Markdown(f"<div class='app-title'>{TRANS['vi']['app_name']}</div>")
206
  txt_desc = gr.Markdown(f"<div class='app-desc'>{TRANS['vi']['app_desc']}</div>")
207
 
208
+ # TOOL 1: PDF
 
 
209
  with gr.Group(visible=True) as group_pdf:
 
210
  with gr.Column(elem_classes="tool-header"):
211
  t1_head = gr.Markdown(f"<div class='tool-title'>{TRANS['vi']['pdf_head']}</div>")
212
  t1_sub = gr.Markdown(f"<div class='tool-sub'>{TRANS['vi']['pdf_sub']}</div>")
213
 
 
214
  with gr.Row(equal_height=True):
 
215
  with gr.Column(variant="panel"):
216
+ in_pdf = gr.File(label=TRANS["vi"]["pdf_label"], file_types=[".pdf"], file_count="multiple", height=250)
217
+ btn_pdf = gr.Button(TRANS["vi"]["pdf_btn"], variant="primary", elem_classes="primary-btn")
 
 
 
 
 
 
 
 
 
 
 
218
  with gr.Column(variant="panel"):
219
+ gr.Markdown("### 📂 Kết quả")
220
  out_word = gr.File(label="Download", height=150)
 
 
221
 
 
222
  # TOOL 2: OCR
 
223
  with gr.Group(visible=False) as group_ocr:
224
  with gr.Column(elem_classes="tool-header"):
225
  t2_head = gr.Markdown(f"<div class='tool-title'>{TRANS['vi']['ocr_head']}</div>")
226
  t2_sub = gr.Markdown(f"<div class='tool-sub'>{TRANS['vi']['ocr_sub']}</div>")
227
 
228
  with gr.Row():
 
229
  with gr.Column(scale=4, variant="panel"):
230
+ in_img = gr.Image(label=TRANS["vi"]["ocr_label"], type="numpy", height=400)
231
+ btn_ocr = gr.Button(TRANS["vi"]["ocr_btn"], variant="primary", elem_classes="primary-btn")
 
 
 
 
 
 
 
 
 
 
 
232
  with gr.Column(scale=6, variant="panel"):
233
  with gr.Tabs():
234
  with gr.TabItem("📝 Text"):
235
+ # FIX LỖI 2: Xóa 'show_copy_button=True' gây crash
236
+ out_txt = gr.Textbox(label=TRANS["vi"]["ocr_rs_text"], lines=14)
 
 
 
237
  out_file = gr.File(label="Download .txt")
 
238
  with gr.TabItem("🖼️ Visualize"):
239
+ out_img_viz = gr.Image(label=TRANS["vi"]["ocr_rs_img"], interactive=False)
 
 
 
240
 
 
241
  txt_footer = gr.Markdown(f"<div class='my-footer'>{TRANS['vi']['footer']}</div>")
242
 
243
+ # EVENTS
 
 
244
  radio_menu.change(toggle_view, [radio_menu, lang_state], [group_pdf, group_ocr])
 
 
245
  btn_pdf.click(convert_pdfs_to_word, [in_pdf, lang_state], out_word)
 
 
246
  btn_ocr.click(run_ocr_func, [in_img, lang_state], [out_txt, out_img_viz, out_file])
 
 
247
  btn_lang.click(change_lang, [lang_state], [
248
  lang_state, btn_lang, radio_menu,
249
  txt_title, txt_desc, txt_footer,
 
251
  t2_head, t2_sub, in_img, btn_ocr, out_txt, out_img_viz
252
  ])
253
 
 
254
  if __name__ == "__main__":
255
+ demo.queue()
256
+ # FIX LỖI 3: Chuyển theme/css xuống đây để tương thích Gradio mới nhất
257
+ demo.launch(
258
+ server_name="0.0.0.0",
259
+ server_port=7860,
260
+ theme=professional_theme,
261
+ css=custom_css
262
+ )