# -*- coding: utf-8 -*- # ============================================================================== # PDF Form Filler & Template Generator with AI Enhancements # # คำอธิบาย: # - เครื่องมือนี้สร้างขึ้นด้วย Gradio สำหรับจัดการไฟล์ PDF และ CSV # - ความสามารถหลัก: # 1. สร้าง Template (CSV/JSON) จากฟอร์ม PDF ที่มีอยู่ # 2. เติมข้อมูลจากไฟล์ CSV ลงในฟอร์ม PDF ทีละหลายๆ ไฟล์ # 3. หาก PDF ไม่มีฟอร์ม จะสร้าง PDF ใหม่จากข้อมูลในแต่ละแถวของ CSV # 4. (ทางเลือก) ใช้ AI และ OCR เพื่อแปลงข้อมูลจากรูปภาพเป็น CSV # 5. (ทางเลือก) ใช้ AI ช่วยแนะนำการจับคู่คอลัมน์ CSV กับช่องใน PDF # 6. (ทางเลือก) ใช้ AI ช่วยตรวจสอบและทำความสะอาดข้อมูลก่อนสร้าง PDF # # การติดตั้ง Dependencies: # - pip install gradio pandas PyPDF2 reportlab # - สำหรับฟีเจอร์ AI/OCR (ทางเลือก): # - pip install Pillow numpy opencv-python pytesseract # - ต้องติดตั้ง Tesseract OCR Engine ในระบบของคุณและตั้งค่า PATH ให้ถูกต้อง # - https://github.com/tesseract-ocr/tesseract # - สำหรับฟีเจอร์ SambaNova AI (ทางเลือก): # - pip install 'gradio_client>=0.12.0' # # ============================================================================== import gradio as gr import pandas as pd import json import io import zipfile from datetime import datetime import traceback import tempfile import os import sys import subprocess # --- ตรวจสอบและติดตั้ง Dependencies --- try: from PyPDF2 import PdfReader, PdfWriter from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont print("Dependencies หลักถูกติดตั้งเรียบร้อยแล้ว") except ImportError: print("กำลังติดตั้ง dependencies ที่จำเป็น: PyPDF2, reportlab, pandas") subprocess.check_call([sys.executable, "-m", "pip", "install", "PyPDF2", "reportlab", "pandas"]) from PyPDF2 import PdfReader, PdfWriter from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter # --- Dependencies เสริมสำหรับ AI และ OCR (จะแจ้งเตือนถ้าไม่มี) --- try: from PIL import Image import numpy as np import cv2 import pytesseract AI_OCR_ENABLED = True print("Dependencies สำหรับ AI/OCR พร้อมใช้งาน") except ImportError: AI_OCR_ENABLED = False print("คำเตือน: ไม่พบ Dependencies สำหรับ AI/OCR (Pillow, numpy, opencv-python, pytesseract)") print("ฟังก์ชันที่เกี่ยวกับรูปภาพและ OCR จะไม่สามารถใช้งานได้") print("ติดตั้งด้วย: pip install Pillow numpy opencv-python pytesseract และติดตั้ง Tesseract engine") try: from gradio_client import Client SAMBANOVA_AI_ENABLED = True print("Dependencies สำหรับ SambaNova AI พร้อมใช้งาน") except ImportError: SAMBANOVA_AI_ENABLED = False print("คำเตือน: ไม่พบ Gradio Client (pip install 'gradio_client>=0.12.0')") print("ฟังก์ชันที่ต้องใช้ AI Model จะไม่สามารถใช้งานได้") # ============================================================================== # ส่วนของฟังก์ชันหลัก (Core Functions) # ============================================================================== def analyze_pdf_fields(pdf_path): """วิเคราะห์ฟิลด์ใน PDF และคืนค่าเป็น Dictionary""" try: reader = PdfReader(pdf_path) all_fields = {} # ตรวจสอบจาก AcroForm if reader.trailer.get("/Root") and reader.trailer["/Root"].get("/AcroForm"): acro_form = reader.trailer["/Root"]["/AcroForm"] if "/Fields" in acro_form: for field in acro_form["/Fields"]: field_obj = field.get_object() if "/T" in field_obj: field_name = str(field_obj["/T"]).strip("()") field_type = str(field_obj.get("/FT", "Unknown")) field_value = str(field_obj.get("/V", "")).strip("()") all_fields[field_name] = { 'type': field_type, 'default_value': field_value, 'method': 'AcroForm' } # ตรวจสอบจาก Annotations ในแต่ละหน้า for page_num, page in enumerate(reader.pages): if "/Annots" in page: for annotation in page["/Annots"]: annot_obj = annotation.get_object() if annot_obj.get("/Subtype") == "/Widget" and "/T" in annot_obj: field_name = str(annot_obj["/T"]).strip("()") if field_name not in all_fields: # เพิ่มเฉพาะที่ยังไม่มี field_type = str(annot_obj.get("/FT", "Widget")) field_value = str(annot_obj.get("/V", "")).strip("()") all_fields[field_name] = { 'type': field_type, 'default_value': field_value, 'page': page_num + 1, 'method': 'Annotation' } return all_fields except Exception as e: return {"error": str(e)} def generate_csv_template(pdf_fields, num_rows=5): """สร้าง CSV template จาก PDF fields""" if not pdf_fields or "error" in pdf_fields: return None, "ไม่สามารถสร้าง CSV template ได้" template_data = {'id': list(range(1, num_rows + 1))} for field_name in pdf_fields.keys(): if field_name and field_name.strip(): clean_name = field_name.strip() sample_value = f"ข้อมูลสำหรับ {clean_name} {{}}" template_data[clean_name] = [sample_value.format(i) for i in range(1, num_rows + 1)] df = pd.DataFrame(template_data) return df, "สร้าง CSV template สำเร็จ" def generate_json_template(pdf_fields): """สร้าง JSON template จาก PDF fields""" if not pdf_fields or "error" in pdf_fields: return None, "ไม่สามารถสร้าง JSON template ได้" template = { "pdf_info": {"total_fields": len(pdf_fields), "generation_time": datetime.now().isoformat()}, "fields": {}, "sample_data": [] } for field_name, field_info in pdf_fields.items(): if field_name and field_name.strip(): template["fields"][field_name.strip()] = field_info for i in range(1, 4): sample_record = {"id": i} for field_name in template["fields"].keys(): sample_record[field_name] = f"ข้อมูลตัวอย่าง {i}" template["sample_data"].append(sample_record) return template, "สร้าง JSON template สำเร็จ" def create_template_files(pdf_file, num_rows, progress=gr.Progress()): """สร้างไฟล์ template (CSV, JSON, README) และรวมเป็น ZIP""" if pdf_file is None: return None, "❌ กรุณาอัพโหลดไฟล์ PDF ก่อน" progress(0, desc="กำลังวิเคราะห์ PDF...") try: pdf_fields = analyze_pdf_fields(pdf_file.name) if not pdf_fields or "error" in pdf_fields: return None, "❌ ไม่พบ Form Fields ใน PDF หรือไฟล์เสียหาย" progress(0.3, desc="กำลังสร้าง CSV template...") csv_df, _ = generate_csv_template(pdf_fields, num_rows) progress(0.6, desc="กำลังสร้าง JSON template...") json_template, _ = generate_json_template(pdf_fields) if csv_df is None or json_template is None: return None, "❌ ไม่สามารถสร้างไฟล์ template ได้" progress(0.8, desc="กำลังบีบอัดไฟล์เป็น ZIP...") zip_buffer = io.BytesIO() timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_f: csv_buffer = io.StringIO() csv_df.to_csv(csv_buffer, index=False, encoding='utf-8-sig') zip_f.writestr(f"template_{timestamp}.csv", csv_buffer.getvalue()) json_str = json.dumps(json_template, ensure_ascii=False, indent=2) zip_f.writestr(f"template_{timestamp}.json", json_str) readme_content = f"""# PDF Form Template Files Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} PDF Fields Found: {len(pdf_fields)} {chr(10).join([f"- {name}" for name in pdf_fields.keys()])} """ zip_f.writestr("README.txt", readme_content) zip_buffer.seek(0) # บันทึกไฟล์ ZIP ชั่วคราวเพื่อให้ Gradio ส่งให้ผู้ใช้ได้ temp_dir = tempfile.gettempdir() zip_filename = f"pdf_templates_{timestamp}.zip" temp_zip_path = os.path.join(temp_dir, zip_filename) with open(temp_zip_path, "wb") as f: f.write(zip_buffer.getvalue()) progress(1, desc="สร้างไฟล์สำเร็จ!") result_msg = f"✅ สร้าง template สำเร็จ!\n- พบ {len(pdf_fields)} fields\n- CSV มี {num_rows} แถวตัวอย่าง" return temp_zip_path, result_msg except Exception as e: return None, f"❌ เกิดข้อผิดพลาด: {e}\n{traceback.format_exc()}" def fill_pdf_form(pdf_path, field_data): """เติมข้อมูลลงในฟอร์มของ PDF""" reader = PdfReader(pdf_path) writer = PdfWriter() writer.append_pages_from_reader(reader) # เติมข้อมูลในฟอร์ม for page in writer.pages: try: writer.update_page_form_field_values(page, field_data, auto_regenerate=False) except Exception: # บางครั้ง field อยูในระดับ root pass try: # ลองเติมที่ root อีกครั้ง writer.update_page_form_field_values(writer.pages[0], field_data) except: pass output_buffer = io.BytesIO() writer.write(output_buffer) output_buffer.seek(0) return output_buffer.getvalue() def create_simple_pdf(data_row, filename): """สร้าง PDF ใหม่แบบง่ายๆ กรณีที่ PDF ต้นฉบับไม่มีฟอร์ม""" buffer = io.BytesIO() # ใช้ font ที่รองรับภาษาไทย try: pdfmetrics.registerFont(TTFont('THSarabunNew', 'THSarabunNew.ttf')) font_name = 'THSarabunNew' except: font_name = 'Helvetica' # Fallback p = canvas.Canvas(buffer, pagesize=letter) width, height = letter p.setFont(font_name, 16) p.drawString(50, height - 50, f"เอกสาร: {filename.replace('.pdf', '')}") p.line(50, height - 60, 550, height - 60) y_position = height - 90 p.setFont(font_name, 12) for column, value in data_row.items(): if pd.notna(value) and str(value).strip(): text = f"{str(column).strip()}: {str(value).strip()}" try: p.drawString(50, y_position, text) except: safe_text = text.encode('latin-1', 'replace').decode('latin-1') p.drawString(50, y_position, safe_text) y_position -= 20 if y_position < 50: p.showPage() p.setFont(font_name, 12) y_position = height - 50 p.save() buffer.seek(0) return buffer.getvalue() def read_csv_safe(csv_file): """อ่านไฟล์ CSV โดยลองหลาย encoding และ separator เพื่อความยืดหยุ่น""" encodings = ['utf-8-sig', 'utf-8', 'cp874', 'tis-620'] separators = [',', ';', '\t'] # ใช้ .name เพราะ Gradio ส่งมาเป็น object ที่มี path อยู่ใน .name filepath = csv_file.name for encoding in encodings: for sep in separators: try: df = pd.read_csv(filepath, encoding=encoding, sep=sep, engine='python') if len(df.columns) > 1: return df, None except Exception: continue return None, "ไม่สามารถอ่านไฟล์ CSV ได้ ลองตรวจสอบ Encoding (ควรเป็น UTF-8) และ Separator (ควรเป็น ,)" def process_pdf_csv(pdf_file, csv_file, filename_column, file_prefix, use_form_fields, progress=gr.Progress()): """ฟังก์ชันหลักสำหรับประมวลผล PDF และ CSV""" if not pdf_file or not csv_file: return None, "❌ กรุณาอัพโหลดทั้งไฟล์ PDF และ CSV" try: df, csv_error = read_csv_safe(csv_file) if df is None: return None, f"❌ ไม่สามารถอ่าน CSV ได้: {csv_error}" pdf_path = pdf_file.name pdf_fields = analyze_pdf_fields(pdf_path) has_form_fields = bool(pdf_fields and "error" not in pdf_fields) generated_pdfs = {} log = [] total_rows = len(df) for index, row in df.iterrows(): progress((index + 1) / total_rows, f"ประมวลผลแถวที่ {index + 1}/{total_rows}") # สร้างชื่อไฟล์ if filename_column and filename_column in df.columns and pd.notna(row[filename_column]): safe_name = "".join(c for c in str(row[filename_column]) if c.isalnum() or c in (' ', '-', '_')).strip() filename = f"{file_prefix}_{safe_name}.pdf" else: filename = f"{file_prefix}_{index + 1:03d}.pdf" row_data = row.to_dict() try: if use_form_fields and has_form_fields: # เติมฟอร์ม PDF ที่มีอยู่ pdf_content = fill_pdf_form(pdf_path, row_data) status = "เติมฟอร์มสำเร็จ" else: # สร้าง PDF ใหม่ pdf_content = create_simple_pdf(row_data, filename) status = "สร้าง PDF ใหม่" if not has_form_fields else "สร้าง PDF ใหม่ (Fallback)" generated_pdfs[filename] = pdf_content log.append(f"✅ {filename}: {status}") except Exception as e: log.append(f"❌ {filename}: เกิดข้อผิดพลาด - {e}") if not generated_pdfs: return None, "❌ ไม่สามารถสร้าง PDF ได้เลย\n" + "\n".join(log) # สร้างไฟล์ ZIP zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_f: for filename, pdf_content in generated_pdfs.items(): zip_f.writestr(filename, pdf_content) zip_f.writestr("processing_log.txt", "\n".join(log)) zip_buffer.seek(0) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") zip_filename = f"generated_pdfs_{timestamp}.zip" temp_zip_path = os.path.join(tempfile.gettempdir(), zip_filename) with open(temp_zip_path, 'wb') as f: f.write(zip_buffer.getvalue()) result_message = f"✅ สร้าง PDF สำเร็จ {len(generated_pdfs)} ไฟล์!\nดูรายละเอียดใน processing_log.txt" return temp_zip_path, result_message except Exception as e: return None, f"❌ เกิดข้อผิดพลาดร้ายแรง: {e}\n{traceback.format_exc()}" # ============================================================================== # ส่วนของฟังก์ชัน AI และ OCR (ทางเลือก) # ============================================================================== def init_sambanova_ai(): """Initialize SambaNova AI model client.""" if not SAMBANOVA_AI_ENABLED: print("SambaNova AI is disabled.") return None try: # ใช้ gradio_client.Client แทน gr.load ที่อาจมีปัญหา client = Client("sambanova/Llama-3-8B-Instruct", hf_token="YOUR_HF_TOKEN") # ใส่ Hugging Face Token ของคุณ print("SambaNova AI client initialized successfully.") return client except Exception as e: print(f"Error initializing SambaNova AI: {e}") return None def extract_text_from_image(image_file): """Extract text from an image file using Tesseract OCR.""" if not AI_OCR_ENABLED or image_file is None: return "", "OCR is not available or no image provided." try: image = Image.open(image_file.name) # ตั้งค่า Tesseract ให้ตรวจจับทั้งภาษาไทยและอังกฤษ custom_config = r'--oem 3 --psm 6 -l tha+eng' text = pytesseract.image_to_string(image, config=custom_config) return text.strip(), "Text extracted successfully." except Exception as e: return "", f"OCR Error: {e}. ตรวจสอบว่าติดตั้ง Tesseract Engine ถูกต้อง" def image_to_csv_with_ai(image_file, progress=gr.Progress()): """Convert data from an image to a CSV file using OCR and AI for structuring.""" if not AI_OCR_ENABLED: return None, "❌ ฟังก์ชันนี้ต้องการ AI/OCR dependencies" if image_file is None: return None, "❌ กรุณาอัพโหลดรูปภาพ" progress(0.2, desc="กำลังอ่านข้อความจากรูปภาพ (OCR)...") raw_text, ocr_status = extract_text_from_image(image_file) if not raw_text: return None, f"❌ ไม่พบข้อความในรูปภาพ: {ocr_status}" progress(0.5, desc="กำลังใช้ AI จัดโครงสร้างข้อมูล...") ai_client = init_sambanova_ai() if not ai_client: return None, "❌ ไม่สามารถเชื่อมต่อ AI Model ได้" prompt = f""" From the following text, extract key-value pairs. The output should be only the data in 'key: value' format, one per line. Example: Name: John Doe Address: 123 Main St Date: 2024-01-15 Text to process: --- {raw_text} --- """ try: # การเรียกใช้งาน API ของ gradio_client result = ai_client.predict(message=prompt, api_name="/chat") progress(0.8, desc="กำลังสร้างไฟล์ CSV...") lines = result.strip().split('\n') data = [line.split(':', 1) for line in lines if ':' in line] if not data: return None, "AI ไม่สามารถจัดโครงสร้างข้อมูลได้" df = pd.DataFrame(data, columns=['Field', 'Value']).set_index('Field').T csv_buffer = io.StringIO() df.to_csv(csv_buffer, index=False, encoding='utf-8-sig') timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") temp_csv_path = os.path.join(tempfile.gettempdir(), f"extracted_data_{timestamp}.csv") with open(temp_csv_path, 'w', encoding='utf-8-sig') as f: f.write(csv_buffer.getvalue()) return temp_csv_path, "✅ แปลงรูปภาพเป็น CSV สำเร็จ" except Exception as e: return None, f"❌ เกิดข้อผิดพลาดระหว่างประมวลผลด้วย AI: {e}" # ============================================================================== # ส่วนของ UI Analysis Functions # ============================================================================== def analyze_pdf_info(pdf_file): """วิเคราะห์และแสดงข้อมูลสรุปของไฟล์ PDF บน UI""" if pdf_file is None: return "ยังไม่มีไฟล์ PDF" try: reader = PdfReader(pdf_file.name) info = f"📄 **ข้อมูล PDF:**\n- จำนวนหน้า: {len(reader.pages)}\n" pdf_fields = analyze_pdf_fields(pdf_file.name) if pdf_fields and "error" not in pdf_fields: info += f"- **พบ Form Fields: {len(pdf_fields)} ช่อง** (จะใช้วิธีเติมฟอร์ม)\n" info += "\n🏷️ **ตัวอย่างชื่อ Fields:**\n" for name in list(pdf_fields.keys())[:10]: info += f" - `{name}`\n" if len(pdf_fields) > 10: info += f" - ... และอีก {len(pdf_fields) - 10} fields\n" else: info += "- **ไม่พบ Form Fields** (จะใช้วิธีสร้าง PDF ใหม่ทับลงบนกระดาษเปล่า)\n" return info except Exception as e: return f"❌ ไม่สามารถวิเคราะห์ PDF: {e}" def analyze_csv_info(csv_file): """วิเคราะห์และแสดงข้อมูลสรุปของไฟล์ CSV และอัปเดต Dropdown""" if csv_file is None: return "ยังไม่มีไฟล์ CSV", gr.update(choices=[], value=None) try: df, error = read_csv_safe(csv_file) if df is None: return f"❌ ไม่สามารถอ่าน CSV: {error}", gr.update(choices=[], value=None) info = f"📋 **ข้อมูล CSV:**\n- จำนวนแถว: {len(df)}\n- จำนวนคอลัมน์: {len(df.columns)}\n" info += "\n📝 **รายชื่อคอลัมน์:**\n" for col in df.columns[:15]: info += f" - `{col}`\n" if len(df.columns) > 15: info += f" - ... และอีก {len(df.columns) - 15} คอลัมน์\n" # อัปเดต Dropdown สำหรับเลือกคอลัมน์ชื่อไฟล์ return info, gr.update(choices=df.columns.tolist(), value=None) except Exception as e: return f"❌ ไม่สามารถวิเคราะห์ CSV: {e}", gr.update(choices=[], value=None) # ============================================================================== # ส่วนของการสร้าง Gradio Interface # ============================================================================== def create_interface(): with gr.Blocks(title="PDF Form Filler & Template Generator", theme=gr.themes.Soft()) as app: gr.Markdown("# 📄 เครื่องมือจัดการ PDF จากข้อมูล CSV") gr.Markdown("รองรับการ **สร้าง Template** จาก PDF, **เติมข้อมูล** จาก CSV, และ **แปลงรูปภาพเป็น CSV** ด้วย AI") with gr.Tabs(): # --- Tab 1: สร้าง Template --- with gr.TabItem("🔄 1. สร้าง Template"): gr.Markdown("## สร้าง CSV/JSON Template จาก PDF ที่มี Form Fields") with gr.Row(): with gr.Column(scale=1): template_pdf = gr.File(label="📄 อัพโหลด PDF ต้นฉบับ", file_types=[".pdf"]) num_sample_rows = gr.Slider(label="จำนวนแถวตัวอย่างใน CSV", minimum=1, maximum=50, value=5, step=1) generate_template_btn = gr.Button("🚀 สร้าง Template", variant="primary") with gr.Column(scale=2): template_pdf_info = gr.Markdown("อัพโหลด PDF เพื่อดูข้อมูล...") template_result_file = gr.File(label="📦 ดาวน์โหลดไฟล์ Template (ZIP)", interactive=False) template_result_message = gr.Markdown() # --- Tab 2: เติมข้อมูล PDF --- with gr.TabItem("📝 2. เติมข้อมูล PDF"): gr.Markdown("## เติมข้อมูลลงใน PDF จากไฟล์ CSV") with gr.Row(): with gr.Column(scale=1): gr.Markdown("### 📂 1. อัพโหลดไฟล์") pdf_file = gr.File(label="📄 PDF Form ต้นฉบับ", file_types=[".pdf"]) csv_file = gr.File(label="📊 CSV ข้อมูล", file_types=[".csv"]) gr.Markdown("### ⚙️ 2. ตั้งค่า") use_form_fields = gr.Checkbox(label="พยายามเติมข้อมูลลงใน Form Fields ที่มีอยู่", value=True) file_prefix = gr.Textbox(label="คำนำหน้าชื่อไฟล์ (Prefix)", value="Document") filename_column = gr.Dropdown(label="เลือกคอลัมน์ที่จะใช้เป็นชื่อไฟล์ (ถ้ามี)", interactive=True) fill_form_btn = gr.Button("🚀 เริ่มเติมข้อมูล", variant="primary") with gr.Column(scale=2): pdf_info = gr.Markdown("อัพโหลด PDF เพื่อดูข้อมูล...") csv_info = gr.Markdown("อัพโหลด CSV เพื่อดูข้อมูล...") gr.Markdown("---") filled_result_file = gr.File(label="📦 ดาวน์โหลด PDF ทั้งหมด (ZIP)", interactive=False) filled_result_message = gr.Markdown() # --- Tab 3: Image to CSV (AI) --- with gr.TabItem("🖼️ 3. แปลงรูปภาพเป็น CSV (AI)"): gr.Markdown("## ใช้ OCR และ AI เพื่อดึงข้อมูลจากรูปภาพและสร้างเป็นไฟล์ CSV") with gr.Row(): with gr.Column(scale=1): image_upload = gr.File(label="🖼️ อัพโหลดรูปภาพ (บิล, เอกสาร, ฯลฯ)", file_types=["image"]) image_to_csv_btn = gr.Button("🤖 แปลงเป็น CSV", variant="primary", visible=AI_OCR_ENABLED) if not AI_OCR_ENABLED: gr.Markdown("⚠️ *ฟังก์ชันนี้ถูกปิดใช้งานเนื่องจากไม่พบ Library ที่จำเป็น (Pillow, OpenCV, Pytesseract)*") with gr.Column(scale=2): image_csv_output = gr.File(label="📄 ดาวน์โหลดไฟล์ CSV ที่ได้", interactive=False) image_csv_message = gr.Markdown() # --- Event Handlers --- template_pdf.change(fn=analyze_pdf_info, inputs=template_pdf, outputs=template_pdf_info) generate_template_btn.click( fn=create_template_files, inputs=[template_pdf, num_sample_rows], outputs=[template_result_file, template_result_message] ) pdf_file.change(fn=analyze_pdf_info, inputs=pdf_file, outputs=pdf_info) csv_file.change(fn=analyze_csv_info, inputs=csv_file, outputs=[csv_info, filename_column]) fill_form_btn.click( fn=process_pdf_csv, inputs=[pdf_file, csv_file, filename_column, file_prefix, use_form_fields], outputs=[filled_result_file, filled_result_message] ) if AI_OCR_ENABLED: image_to_csv_btn.click( fn=image_to_csv_with_ai, inputs=[image_upload], outputs=[image_csv_output, image_csv_message] ) return app # --- Launch the application --- if __name__ == "__main__": # ลองหา font ไทย ถ้าไม่มีจะได้ไม่ error ตอนสร้าง PDF try: from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont # สำหรับ Windows pdfmetrics.registerFont(TTFont('THSarabunNew', 'C:/Windows/Fonts/THSARI.TTF')) print("ลงทะเบียน Font 'THSarabunNew' สำหรับ ReportLab สำเร็จ") except: print("คำเตือน: ไม่พบ Font 'THSarabunNew' ในระบบ อาจทำให้การสร้าง PDF ภาษาไทยมีปัญหา") print("แนะนำให้ติดตั้งฟอนต์ TH SarabunPSK หรือปรับแก้ path ของฟอนต์ในโค้ด") app = create_interface() app.launch(debug=True)