VietKien commited on
Commit
83cd348
·
verified ·
1 Parent(s): b731151

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +255 -171
app.py CHANGED
@@ -2,220 +2,304 @@ import gradio as gr
2
  from pdf2docx import Converter
3
  import os
4
  import zipfile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  # --- 1. CẤU HÌNH NGÔN NGỮ (TỪ ĐIỂN) ---
7
- # Tại đây chứa tất cả văn bản hiển thị
8
  TRANS = {
9
  "vi": {
10
- "lang_btn": "🇬🇧 Switch to English",
11
- "title": "# 📝 CHUYỂN ĐỔI PDF SANG WORD\n### Công cụ hỗ trợ Đề thi & Tài liệu (Hỗ trợ nhiều file)",
12
- "input_group": "### 1. Tải tài liệu",
13
- "input_label": "Chọn file PDF (Giữ Shift/Ctrl để chọn nhiều)",
14
- "output_group": "### 2. Tải về kết quả",
15
- "output_label": "File Word hoặc ZIP",
16
- "status_label": "Trạng thái",
17
- "convert_btn": "🚀 BẮT ĐẦU CHUYỂN ĐỔI",
18
- "info_accordion": "ℹ️ Thông tin phát triển & Bản quyền",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  "info_content": """
20
- ### 👨‍💻 Thông tin tác giả
21
- * **Phát triển bởi:** Chu Viết Kiên
22
- * **Liên hệ:** kiencv.3107@gmail.com
23
- * **Phiên bản:** 1.2.0 (Multi-language)
24
-
25
- ### 📜 Bản quyền
26
- * Công cụ sử dụng thư viện `pdf2docx`.
27
- * File sẽ tự động xóa sau khi phiên làm việc kết thúc.
28
- """,
29
- # Các thông báo trong code xử lý
30
- "msg_warning": "⚠️ Vui lòng tải ít nhất một file lên!",
31
- "msg_process": "Đang xử lý file",
32
- "msg_zip": "Đang đóng gói file...",
33
- "msg_done": "Xong!",
34
- "msg_success_single": "✅ Hoàn tất! Đã chuyển đổi:",
35
- "msg_success_multi": "✅ Hoàn tất! Đã nén {n} file vào ZIP.",
36
- "msg_error": "❌ Có lỗi xảy ra:"
37
  },
38
  "en": {
39
- "lang_btn": "🇻🇳 Chuyển sang Tiếng Việt",
40
- "title": "# 📝 PDF TO WORD CONVERTER\n### Professional Tool for Documents (Batch Support)",
41
- "input_group": "### 1. Upload Documents",
42
- "input_label": "Select PDF Files (Hold Shift/Ctrl to select multiple)",
43
- "output_group": "### 2. Download Result",
44
- "output_label": "Word File or ZIP",
45
- "status_label": "Status",
46
- "convert_btn": "🚀 CONVERT NOW",
47
- "info_accordion": "ℹ️ Developer Info & Copyright",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  "info_content": """
49
- ### 👨‍💻 Author Info
50
- * **Developer:** Chu Viet Kien
51
- * **Contact:** kiencv.3107@gmail.com
52
- * **Version:** 1.2.0 (Multi-language)
53
-
54
- ### 📜 Disclaimer
55
- * This tool uses `pdf2docx` library.
56
- * Files are automatically deleted after the session.
57
- """,
58
- # Messages inside logic
59
- "msg_warning": "⚠️ Please upload at least one file!",
60
- "msg_process": "Processing file",
61
- "msg_zip": "Zipping files...",
62
- "msg_done": "Done!",
63
- "msg_success_single": "✅ Success! Converted:",
64
- "msg_success_multi": "✅ Success! Compressed {n} files into ZIP.",
65
- "msg_error": "❌ Error occurred:"
66
  }
67
  }
68
 
69
  # --- 2. HÀM XỬ LÝ LOGIC ---
 
 
70
  def convert_pdfs_to_word(pdf_files, lang_code, progress=gr.Progress()):
71
- # Lấy từ điển ngôn ngữ hiện tại
72
  T = TRANS[lang_code]
73
-
74
- if not pdf_files:
75
- return None, T["msg_warning"]
76
 
77
- if not isinstance(pdf_files, list):
78
- pdf_files = [pdf_files]
79
-
80
  converted_files = []
81
- total_files = len(pdf_files)
82
 
83
  try:
84
- for index, pdf_file in enumerate(pdf_files):
85
- original_name = os.path.basename(pdf_file.name)
86
- docx_filename = os.path.splitext(original_name)[0] + ".docx"
87
-
88
- # Thông báo tiến trình đa ngôn ngữ
89
- msg = f"{T['msg_process']} {index + 1}/{total_files}: {original_name}"
90
- progress((index / total_files), desc=msg)
91
-
92
  cv = Converter(pdf_file.name)
93
- cv.convert(docx_filename, start=0, end=None)
94
  cv.close()
95
- converted_files.append(docx_filename)
96
-
97
- progress(0.95, desc=T["msg_zip"])
98
-
99
  if len(converted_files) == 1:
100
- final_output = converted_files[0]
101
- status_msg = f"{T['msg_success_single']} {os.path.basename(final_output)}"
102
  else:
103
- zip_filename = "Converted_Documents.zip"
104
- with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
105
- for file in converted_files:
106
- zipf.write(file, arcname=os.path.basename(file))
107
- final_output = zip_filename
108
- status_msg = T["msg_success_multi"].format(n=len(converted_files))
 
 
 
 
 
 
109
 
110
- progress(1.0, desc=T["msg_done"])
111
- return final_output, status_msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  except Exception as e:
114
- return None, f"{T['msg_error']} {str(e)}"
115
 
116
- # --- 3. HÀM ĐỔI NGÔN NGỮ GIAO DIỆN ---
117
- def change_language(current_lang):
118
- # Đảo ngược ngôn ngữ
119
- new_lang = "en" if current_lang == "vi" else "vi"
120
  T = TRANS[new_lang]
121
 
122
- # Trả về updates cho từng thành phần UI
123
  return (
124
- new_lang, # Cập nhật State
125
- T["lang_btn"], # Cập nhật nút đổi ngôn ngữ
126
- T["title"], # Cập nhật Tiêu đề
127
- T["input_group"], # Cập nhật Group Input
128
- gr.update(label=T["input_label"]), # Cập nhật Label Input File
129
- T["output_group"], # Cập nhật Group Output
130
- gr.update(label=T["output_label"]), # Cập nhật Label Output File
131
- gr.update(label=T["status_label"]), # Cập nhật Label Status
132
- T["convert_btn"], # Cập nhật Nút Convert
133
- gr.update(label=T["info_accordion"]), # Cập nhật Tiêu đề Accordion
134
- T["info_content"] # Cập nhật Nội dung Accordion
 
 
 
 
 
 
135
  )
136
 
137
- # --- 4. GIAO DIỆN (UI) ---
 
 
 
 
 
 
 
 
 
138
  custom_css = """
139
- #convert_btn {
140
- background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
141
- color: white; border: none; font-weight: bold; font-size: 16px;
142
- }
143
- #lang_btn {
144
- border: 1px solid #ccc; color: #333;
145
- }
146
  footer {visibility: hidden}
147
  """
148
 
149
- theme = gr.themes.Soft(primary_hue="blue", secondary_hue="slate")
150
-
151
- with gr.Blocks(theme=theme, css=custom_css, title="PDF to Word Pro") as demo:
152
 
153
- # Biến trạng thái lưu ngôn ngữ (Mặc định là 'vi')
154
  lang_state = gr.State(value="vi")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
- # --- Hàng nút đổi ngôn ngữ (Góc phải) ---
157
- with gr.Row():
158
- with gr.Column(scale=9): pass # Khoảng trống
159
- with gr.Column(scale=1):
160
- btn_change_lang = gr.Button(TRANS["vi"]["lang_btn"], elem_id="lang_btn", size="sm")
161
-
162
- # --- Tiêu đề ---
163
- header_md = gr.Markdown(TRANS["vi"]["title"])
164
- gr.HTML("<hr>")
165
-
166
- # --- Nội dung chính ---
167
- with gr.Row():
168
- # Input
169
- with gr.Column(variant="panel"):
170
- input_group_md = gr.Markdown(TRANS["vi"]["input_group"])
171
- input_files = gr.File(
172
- label=TRANS["vi"]["input_label"],
173
- file_types=[".pdf"], file_count="multiple", height=250
174
- )
 
 
 
175
 
176
- # Output
177
- with gr.Column(variant="panel"):
178
- output_group_md = gr.Markdown(TRANS["vi"]["output_group"])
179
- output_file = gr.File(label=TRANS["vi"]["output_label"], height=150)
180
- status_text = gr.Textbox(label=TRANS["vi"]["status_label"], interactive=False)
181
-
182
- # Nút bấm
183
- with gr.Row():
184
- convert_btn = gr.Button(TRANS["vi"]["convert_btn"], elem_id="convert_btn", scale=1)
185
-
186
- # --- Footer ---
187
- gr.HTML("<br>")
188
- with gr.Accordion(label=TRANS["vi"]["info_accordion"], open=False) as info_acc:
189
- info_md = gr.Markdown(TRANS["vi"]["info_content"])
190
-
191
- # --- CÁC SỰ KIỆN (EVENTS) ---
192
-
193
- # 1. Sự kiện đổi ngôn ngữ
194
- btn_change_lang.click(
195
- fn=change_language,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  inputs=[lang_state],
197
  outputs=[
198
- lang_state, # 0. State mới
199
- btn_change_lang, # 1. Nút đổi ngôn ngữ
200
- header_md, # 2. Tiêu đề
201
- input_group_md, # 3. Tiêu đề input
202
- input_files, # 4. Label input file
203
- output_group_md, # 5. Tiêu đề output
204
- output_file, # 6. Label output file
205
- status_text, # 7. Label status
206
- convert_btn, # 8. Nút convert
207
- info_acc, # 9. Tiêu đề Accordion
208
- info_md # 10. Nội dung Accordion
209
  ]
210
  )
211
 
212
- # 2. Sự kiện Convert (Truyền thêm lang_state để biết hiện thông báo tiếng gì)
213
- convert_btn.click(
214
- fn=convert_pdfs_to_word,
215
- inputs=[input_files, lang_state], # Input file và Ngôn ngữ hiện tại
216
- outputs=[output_file, status_text],
217
- show_progress="hidden"
218
- )
219
-
220
  if __name__ == "__main__":
221
  demo.launch()
 
2
  from pdf2docx import Converter
3
  import os
4
  import zipfile
5
+ import cv2
6
+ import numpy as np
7
+ from PIL import Image
8
+
9
+ # Thử import PaddleOCR, nếu chưa cài thì báo lỗi nhẹ
10
+ try:
11
+ from paddleocr import PaddleOCR
12
+ # Khởi tạo model OCR (tải model lần đầu sẽ hơi lâu)
13
+ # lang='vi' để hỗ trợ tiếng Việt
14
+ ocr_engine = PaddleOCR(use_angle_cls=True, lang='vi', show_log=False)
15
+ HAS_OCR = True
16
+ except ImportError:
17
+ HAS_OCR = False
18
+ print("⚠️ CHƯA CÀI PADDLEOCR: Vui lòng chạy 'pip install paddlepaddle paddleocr'")
19
 
20
  # --- 1. CẤU HÌNH NGÔN NGỮ (TỪ ĐIỂN) ---
 
21
  TRANS = {
22
  "vi": {
23
+ "lang_btn": "🇬🇧 English",
24
+ "app_title": "DOCUMENTS TOOLKIT PRO",
25
+ # Sidebar items
26
+ "menu_label": "🛠️ Chọn Chức năng",
27
+ "menu_pdf": "📝 Chuyển PDF sang Word",
28
+ "menu_ocr": "👁️ OCR (Trích xuất chữ từ ảnh)",
29
+ "info_header": "ℹ️ Thông tin",
30
+
31
+ # PDF Tool
32
+ "pdf_title": "### 📄 CHUYỂN ĐỔI PDF SANG WORD",
33
+ "pdf_input_label": "Tải file PDF (Chọn nhiều file)",
34
+ "pdf_btn": "🚀 CHUYỂN ĐỔI NGAY",
35
+
36
+ # OCR Tool
37
+ "ocr_title": "### 👁️ TRÍCH XUẤT VĂN BẢN (OCR)",
38
+ "ocr_input_label": "Tải hình ảnh (PNG, JPG)",
39
+ "ocr_btn": "🔍 QUÉT VÀ TRÍCH XUẤT",
40
+ "ocr_output_text": "Nội dung văn bản",
41
+ "ocr_output_img": "Ảnh đã khoanh vùng chữ",
42
+
43
+ # Messages
44
+ "msg_warning": "⚠️ Vui lòng tải file lên!",
45
+ "msg_processing": "Đang xử lý...",
46
+ "msg_success": "✅ Hoàn tất!",
47
+ "msg_error": "❌ Lỗi: ",
48
+ "ocr_not_installed": "⚠️ Lỗi: Chưa cài thư viện PaddleOCR.",
49
+
50
+ # Info Content
51
  "info_content": """
52
+ **Phát triển bởi:** Chu Viết Kiên
53
+ **Liên hệ:** kiencv.3107@gmail.com
54
+ **Phiên bản:** 2.0 (OCR Update)
55
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  },
57
  "en": {
58
+ "lang_btn": "🇻🇳 Tiếng Việt",
59
+ "app_title": "DOCUMENTS TOOLKIT PRO",
60
+ # Sidebar items
61
+ "menu_label": "🛠️ Select Tool",
62
+ "menu_pdf": "📝 PDF to Word Converter",
63
+ "menu_ocr": "👁️ OCR (Image to Text)",
64
+ "info_header": "ℹ️ Information",
65
+
66
+ # PDF Tool
67
+ "pdf_title": "### 📄 PDF TO WORD CONVERTER",
68
+ "pdf_input_label": "Upload PDF Files",
69
+ "pdf_btn": "🚀 CONVERT NOW",
70
+
71
+ # OCR Tool
72
+ "ocr_title": "### 👁️ OPTICAL CHARACTER RECOGNITION (OCR)",
73
+ "ocr_input_label": "Upload Image (PNG, JPG)",
74
+ "ocr_btn": "🔍 SCAN & EXTRACT",
75
+ "ocr_output_text": "Extracted Text",
76
+ "ocr_output_img": "Annotated Image",
77
+
78
+ # Messages
79
+ "msg_warning": "⚠️ Please upload a file!",
80
+ "msg_processing": "Processing...",
81
+ "msg_success": "✅ Done!",
82
+ "msg_error": "❌ Error: ",
83
+ "ocr_not_installed": "⚠️ Error: PaddleOCR library not installed.",
84
+
85
+ # Info Content
86
  "info_content": """
87
+ **Developer:** Chu Viet Kien
88
+ **Contact:** kiencv.3107@gmail.com
89
+ **Version:** 2.0 (OCR Update)
90
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
  }
93
 
94
  # --- 2. HÀM XỬ LÝ LOGIC ---
95
+
96
+ # 2.1 Logic PDF -> Word
97
  def convert_pdfs_to_word(pdf_files, lang_code, progress=gr.Progress()):
 
98
  T = TRANS[lang_code]
99
+ if not pdf_files: return None, T["msg_warning"]
 
 
100
 
101
+ if not isinstance(pdf_files, list): pdf_files = [pdf_files]
 
 
102
  converted_files = []
 
103
 
104
  try:
105
+ for idx, pdf_file in enumerate(pdf_files):
106
+ progress((idx / len(pdf_files)), desc=f"Processing {os.path.basename(pdf_file.name)}...")
107
+ docx_name = os.path.splitext(os.path.basename(pdf_file.name))[0] + ".docx"
 
 
 
 
 
108
  cv = Converter(pdf_file.name)
109
+ cv.convert(docx_name, start=0, end=None)
110
  cv.close()
111
+ converted_files.append(docx_name)
112
+
 
 
113
  if len(converted_files) == 1:
114
+ return converted_files[0], T["msg_success"]
 
115
  else:
116
+ zip_name = "Converted_Docs.zip"
117
+ with zipfile.ZipFile(zip_name, 'w') as zf:
118
+ for f in converted_files: zf.write(f)
119
+ return zip_name, T["msg_success"]
120
+ except Exception as e:
121
+ return None, f"{T['msg_error']} {str(e)}"
122
+
123
+ # 2.2 Logic OCR
124
+ def run_ocr_func(image, lang_code):
125
+ T = TRANS[lang_code]
126
+ if not HAS_OCR: return None, None, T["ocr_not_installed"]
127
+ if image is None: return None, None, T["msg_warning"]
128
 
129
+ try:
130
+ # PaddleOCR nhận đường dẫn file hoặc numpy array
131
+ # Gradio Image input mặc định trả về numpy array (RGB)
132
+
133
+ # Chạy OCR
134
+ result = ocr_engine.ocr(image, cls=True)
135
+
136
+ # Xử lý kết quả
137
+ txts = []
138
+ boxes = []
139
+ scores = []
140
+
141
+ # Result cấu trúc: [ [ [box], (text, score) ], ... ]
142
+ # Đôi khi result trả về list lồng nhau nếu có nhiều vùng
143
+ if result and result[0]:
144
+ for line in result[0]:
145
+ boxes.append(line[0])
146
+ txts.append(line[1][0])
147
+ scores.append(line[1][1])
148
+
149
+ # Tạo văn bản kết quả
150
+ full_text = "\n".join(txts)
151
+
152
+ # Vẽ box lên ảnh để hiển thị (Dùng OpenCV)
153
+ # Convert RGB (Gradio) -> BGR (OpenCV)
154
+ img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
155
+ for box in boxes:
156
+ box = np.array(box).astype(np.int32).reshape((-1, 1, 2))
157
+ cv2.polylines(img_cv, [box], True, (0, 0, 255), 2)
158
+
159
+ # Convert ngược lại BGR -> RGB để hiển thị trên Gradio
160
+ final_img = cv2.cvtColor(img_cv, cv2.COLOR_BGR2RGB)
161
+
162
+ # Lưu file text để tải về (tuỳ chọn)
163
+ with open("ocr_result.txt", "w", encoding="utf-8") as f:
164
+ f.write(full_text)
165
+
166
+ return full_text, final_img, "ocr_result.txt"
167
 
168
  except Exception as e:
169
+ return str(e), None, None
170
 
171
+ # --- 3. UI HELPER FUNCTIONS ---
172
+
173
+ def change_ui_language(lang):
174
+ new_lang = "en" if lang == "vi" else "vi"
175
  T = TRANS[new_lang]
176
 
 
177
  return (
178
+ new_lang, # Update State
179
+ T["lang_btn"],
180
+ gr.update(label=T["menu_label"], choices=[T["menu_pdf"], T["menu_ocr"]], value=T["menu_pdf"]), # Radio
181
+ T["info_header"], # Accordion Label
182
+ T["info_content"], # Info Text
183
+
184
+ # PDF UI
185
+ T["pdf_title"],
186
+ gr.update(label=T["pdf_input_label"]),
187
+ T["pdf_btn"],
188
+
189
+ # OCR UI
190
+ T["ocr_title"],
191
+ gr.update(label=T["ocr_input_label"]),
192
+ T["ocr_btn"],
193
+ gr.update(label=T["ocr_output_text"]),
194
+ gr.update(label=T["ocr_output_img"])
195
  )
196
 
197
+ def toggle_tool(menu_choice, lang_code):
198
+ # Hàm này quyết định ẩn hiện Group nào dựa trên Radio Button
199
+ T = TRANS[lang_code]
200
+ if menu_choice == T["menu_pdf"] or menu_choice == TRANS["en"]["menu_pdf"] or menu_choice == TRANS["vi"]["menu_pdf"]:
201
+ return gr.update(visible=True), gr.update(visible=False)
202
+ else:
203
+ return gr.update(visible=False), gr.update(visible=True)
204
+
205
+ # --- 4. GIAO DIỆN CHÍNH ---
206
+
207
  custom_css = """
208
+ .gradio-container {background-color: #f9f9f9}
209
+ #btn_convert {background: linear-gradient(90deg, #4b6cb7, #182848); color: white;}
210
+ #btn_ocr {background: linear-gradient(90deg, #d53369, #daae51); color: white;}
 
 
 
 
211
  footer {visibility: hidden}
212
  """
213
 
214
+ with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="Toolkit Pro") as demo:
 
 
215
 
 
216
  lang_state = gr.State(value="vi")
217
+
218
+ # --- SIDEBAR (THANH BÊN) ---
219
+ with gr.Sidebar():
220
+ gr.Markdown("## 🛠️ CONTROL PANEL")
221
+
222
+ # 1. Nút đổi ngôn ngữ
223
+ btn_lang = gr.Button(TRANS["vi"]["lang_btn"])
224
+
225
+ # 2. Menu chọn công cụ
226
+ radio_menu = gr.Radio(
227
+ choices=[TRANS["vi"]["menu_pdf"], TRANS["vi"]["menu_ocr"]],
228
+ value=TRANS["vi"]["menu_pdf"],
229
+ label=TRANS["vi"]["menu_label"],
230
+ interactive=True
231
+ )
232
+
233
+ gr.HTML("<hr>")
234
+
235
+ # 3. Thông tin tác giả
236
+ with gr.Accordion(TRANS["vi"]["info_header"], open=True) as info_acc:
237
+ info_md = gr.Markdown(TRANS["vi"]["info_content"])
238
 
239
+ # --- MAIN CONTENT AREA ---
240
+
241
+ # === GROUP 1: PDF TO WORD ===
242
+ with gr.Group(visible=True) as group_pdf:
243
+ header_pdf = gr.Markdown(TRANS["vi"]["pdf_title"])
244
+ with gr.Row():
245
+ with gr.Column():
246
+ input_pdf = gr.File(label=TRANS["vi"]["pdf_input_label"], file_types=[".pdf"], file_count="multiple", height=200)
247
+ with gr.Column():
248
+ output_docx = gr.File(label="Download Result", height=100)
249
+ status_pdf = gr.Textbox(label="Status", interactive=False)
250
+
251
+ btn_run_pdf = gr.Button(TRANS["vi"]["pdf_btn"], elem_id="btn_convert")
252
+
253
+ # === GROUP 2: OCR ===
254
+ with gr.Group(visible=False) as group_ocr:
255
+ header_ocr = gr.Markdown(TRANS["vi"]["ocr_title"])
256
+ with gr.Row():
257
+ # Cột trái: Input
258
+ with gr.Column(scale=1):
259
+ input_img = gr.Image(label=TRANS["vi"]["ocr_input_label"], type="numpy", height=300)
260
+ btn_run_ocr = gr.Button(TRANS["vi"]["ocr_btn"], elem_id="btn_ocr")
261
 
262
+ # Cột phải: Output
263
+ with gr.Column(scale=1):
264
+ output_text = gr.Textbox(label=TRANS["vi"]["ocr_output_text"], lines=10, show_copy_button=True)
265
+ output_txt_file = gr.File(label="Download .txt")
266
+
267
+ # Hiển thị ảnh kết quả bên dưới
268
+ output_img_viz = gr.Image(label=TRANS["vi"]["ocr_output_img"], interactive=False)
269
+
270
+ # --- EVENT HANDLERS ---
271
+
272
+ # 1. Logic chạy PDF
273
+ btn_run_pdf.click(
274
+ fn=convert_pdfs_to_word,
275
+ inputs=[input_pdf, lang_state],
276
+ outputs=[output_docx, status_pdf]
277
+ )
278
+
279
+ # 2. Logic chạy OCR
280
+ btn_run_ocr.click(
281
+ fn=run_ocr_func,
282
+ inputs=[input_img, lang_state],
283
+ outputs=[output_text, output_img_viz, output_txt_file]
284
+ )
285
+
286
+ # 3. Logic chuyển đổi Tab (Ẩn hiện Group)
287
+ radio_menu.change(
288
+ fn=toggle_tool,
289
+ inputs=[radio_menu, lang_state],
290
+ outputs=[group_pdf, group_ocr]
291
+ )
292
+
293
+ # 4. Logic đổi ngôn ngữ (Cập nhật toàn bộ Label)
294
+ btn_lang.click(
295
+ fn=change_ui_language,
296
  inputs=[lang_state],
297
  outputs=[
298
+ lang_state, btn_lang, radio_menu, info_acc, info_md, # Sidebar updates
299
+ header_pdf, input_pdf, btn_run_pdf, # PDF updates
300
+ header_ocr, input_img, btn_run_ocr, output_text, output_img_viz # OCR updates
 
 
 
 
 
 
 
 
301
  ]
302
  )
303
 
 
 
 
 
 
 
 
 
304
  if __name__ == "__main__":
305
  demo.launch()