File size: 5,590 Bytes
420a76d
 
 
 
 
 
61935d4
caeff2f
420a76d
61935d4
 
420a76d
61935d4
 
420a76d
61935d4
3d59ca3
a31ce93
 
3d59ca3
 
 
 
caeff2f
3d59ca3
420a76d
 
 
1ad2e0c
3d59ca3
420a76d
 
61935d4
 
 
 
 
 
 
 
420a76d
 
61935d4
3d59ca3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420a76d
 
61935d4
420a76d
3d59ca3
 
 
 
caeff2f
 
 
3d59ca3
420a76d
 
 
61935d4
420a76d
 
 
 
61935d4
e68ec86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61935d4
420a76d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import gradio as gr
import pandas as pd
from datetime import datetime
import json
import io
from PIL import Image
import pytesseract # THAY ĐỔI 1: Thư viện OCR mới
from ctransformers import AutoModelForCausalLM as CAutoModelForCausalLM

# --- CẤU HÌNH VÀ TẢI MÔ HÌNH (PHIÊN BẢN SIÊU NHẸ) ---
print("Ứng dụng đang khởi động...")

# THAY ĐỔI 2: XÓA HOÀN TOÀN KHỐI TẢI MÔ HÌNH OCR NẶNG NỀ
print("Sử dụng Tesseract OCR (siêu nhẹ).")

# DÙNG CTRANSFORMERS ĐỂ TẢI GGUF (Giữ nguyên)
print("Đang tải mô hình LLM (Llama-3-8B GGUF for CPU)...")
llm_model_id = "bartowski/Meta-Llama-3-8B-Instruct-GGUF"
llm_model_file = "Meta-Llama-3-8B-Instruct-Q4_K_M.gguf"
llm = CAutoModelForCausalLM.from_pretrained(
    llm_model_id,
    model_file=llm_model_file,
    model_type="llama",
    gpu_layers=0,
    context_length=4096 
)
print("Tải xong mô hình LLM.")


# --- CÁC HÀM XỬ LÝ ---

def run_ocr(image: Image.Image) -> str:
    # THAY ĐỔI 3: VIẾT LẠI HOÀN TOÀN HÀM OCR
    """Hàm chạy Tesseract OCR để đọc chữ từ ảnh"""
    try:
        text = pytesseract.image_to_string(image)
        return text
    except Exception as e:
        print(f"Lỗi Tesseract: {e}")
        return "Lỗi khi đọc chữ từ ảnh."

def extract_order_from_text(text: str) -> dict:
    # (Giữ nguyên không thay đổi)
    prompt = f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>
    You are an expert assistant that only outputs valid JSON. Extract order information from the text. The JSON object must contain "ten_khach_hang" (string, null if not found) and "danh_sach_hang" (an array of items). Each item must have "ten_hang" (string), "so_luong" (number), "don_vi" (string), "ma_hang" (string, null if not found), and "ghi_chu" (string, null if not found).<|eot_id|><|start_header_id|>user<|end_header_id|>
    Text Content:
    {text}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
    """
    response_text = llm(prompt, max_new_tokens=1024, temperature=0.1, stop=["<|eot_id|>"])
    try:
        json_str = response_text.strip()
        start = json_str.find('{')
        end = json_str.rfind('}') + 1
        if start != -1 and end != 0:
            json_str = json_str[start:end]
        return json.loads(json_str)
    except json.JSONDecodeError:
        return {"error": "AI trả về định dạng không hợp lệ", "raw_response": response_text}

def create_excel_file(order_data: dict):
    # (Giữ nguyên không thay đổi)
    if not order_data or "danh_sach_hang" not in order_data or not order_data["danh_sach_hang"]: return None
    flat_data = []
    customer = order_data.get('ten_khach_hang', 'N/A')
    for item in order_data['danh_sach_hang']:
        flat_data.append({
            'Khách hàng': customer, 'Mã hàng': item.get('ma_hang'),
            'Tên hàng': item.get('ten_hang'), 'Số lượng': item.get('so_luong'),
            'Đơn vị': item.get('don_vi'), 'Ghi chú': item.get('ghi_chu')
        })
    df = pd.DataFrame(flat_data)
    output = io.BytesIO()
    with pd.ExcelWriter(output, engine='openpyxl') as writer: df.to_excel(writer, index=False, sheet_name='DonHang')
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"don_hang_{timestamp}.xlsx"
    return (filename, output.getvalue())

def process_image_and_extract(image):
    # (Giữ nguyên không thay đổi)
    try:
        if image is None: return "Vui lòng dán ảnh vào.", None, None
        extracted_text = run_ocr(image)
        if not extracted_text.strip(): return "Không đọc được chữ từ hình ảnh.", None, None
        order_data = extract_order_from_text(extracted_text)
        if "error" in order_data: return extracted_text, f"Lỗi từ AI: {order_data['error']}\nPhản hồi gốc: {order_data['raw_response']}", None
        excel_info = create_excel_file(order_data)
        df_display = pd.DataFrame(order_data.get('danh_sach_hang', []))
        if excel_info:
            filename, filebytes = excel_info
            with open(filename, "wb") as f: f.write(filebytes)
            return extracted_text, df_display, filename
        else: return extracted_text, df_display, None
    except Exception as e:
        import traceback
        error_str = str(e)
        traceback_str = traceback.format_exc()
        print(traceback_str)
        return f"Lỗi nghiêm trọng: {error_str}", None, None

# --- XÂY DỰNG GIAO DIỆN GRADIO (GIỮ NGUYÊN) ---
with gr.Blocks(theme=gr.themes.Soft()) as app:
    gr.Markdown("# Ứng dụng Trích xuất Đơn hàng từ Ảnh chụp màn hình")
    gr.Markdown("Chụp màn hình email/tin nhắn đặt hàng, sau đó dán (Ctrl+V) vào ô bên dưới và nhấn 'Xử lý'.")
    with gr.Row():
        with gr.Column(scale=1):
            image_input = gr.Image(label="Dán ảnh chụp màn hình vào đây", type="pil", sources=["clipboard", "upload"])
            process_btn = gr.Button("Xử lý", variant="primary")
        with gr.Column(scale=2):
            gr.Markdown("### Kết quả trích xuất")
            output_table = gr.DataFrame(label="Chi tiết đơn hàng")
            output_excel = gr.File(label="Tải file Excel")
            gr.Markdown("### Văn bản đọc được từ ảnh (OCR)")
            output_text = gr.Textbox(label="Text from Image", lines=10, interactive=False)
    process_btn.click(fn=process_image_and_extract, inputs=image_input, outputs=[output_text, output_table, output_excel])

app.launch(debug=True)