Lucas Gagneten
less log
0f407f9
# interface.py
"""
Interfaz de usuario con Gradio
"""
import gradio as gr
from config import HUGGINGFACE_MODEL
class GradioInterface:
"""Clase para construir y gestionar la interfaz Gradio."""
def __init__(self, batch_processor, result_navigator):
"""
Inicializa la interfaz.
Args:
batch_processor: Instancia de BatchProcessor
result_navigator: Clase ResultNavigator (no instancia)
"""
self.batch_processor = batch_processor
self.navigator = result_navigator
def update_dataframe_with_edits(self, master_df, current_index, edited_table):
"""
Actualiza el DataFrame maestro con las ediciones del usuario.
Args:
master_df: DataFrame maestro
current_index: Índice actual
edited_table: Tabla editada
Returns:
pd.DataFrame: DataFrame actualizado
"""
if master_df is None:
return master_df
updated_df = self.navigator.update_df_from_table(master_df, current_index, edited_table)
return updated_df
def verify_and_generate_excel(self, master_df):
"""
Verifica el DataFrame y genera el Excel.
Args:
master_df: DataFrame maestro
Returns:
tuple: (filepath, mensaje)
"""
from datetime import datetime
import os
if master_df is None or master_df.empty:
return None, "❌ No hay datos para exportar"
# Generar archivo
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"facturas_lote_{timestamp}.xlsx"
filepath = os.path.join(os.getcwd(), filename)
master_df.to_excel(filepath, index=False, engine='openpyxl')
mensaje = f"✓ Excel generado: {filename} ({len(master_df)} facturas)"
return filepath, mensaje
def enable_buttons(self, master_df):
"""Habilita botones si hay DataFrame."""
has_data = master_df is not None and not master_df.empty
return (
gr.update(interactive=has_data), # prev
gr.update(interactive=has_data), # save
gr.update(interactive=has_data), # next
gr.update(interactive=has_data) # download
)
def build_interface(self):
"""Construye la interfaz Gradio."""
with gr.Blocks(title="NER de Facturas Argentinas por Lote") as demo:
gr.Markdown(
f"""
# 🇦🇷 Extracción de Datos de Facturas Argentinas
Carga las facutas para su procesamiento por Lote
Se utiliza **LayoutLMv3** (`{HUGGINGFACE_MODEL}`) y **DocTR** forzando la **ejecución en CPU**.
"""
)
# ÚNICO ESTADO: El DataFrame maestro
master_dataframe_state = gr.State(value=None)
current_index_state = gr.State(value=0)
# Sección de carga
with gr.Row():
with gr.Column(scale=1):
file_input = gr.Files(
file_count="multiple",
type="filepath",
label="📂 Cargar las Facturas",
interactive=True
)
with gr.Column(scale=1):
with gr.Row():
process_button = gr.Button(
"🚀 Procesar Lote de Facturas",
variant="primary",
size="lg",
scale=2
)
download_button = gr.Button(
"📥 Descargar XLSX",
variant="secondary",
size="lg",
scale=1,
interactive=False
)
status_output = gr.Textbox(
label="📊 Estado",
value="Carga tus facturas y haz clic en 'Procesar'",
interactive=False
)
gr.Markdown("---")
excel_file = gr.File(label="📥 Archivo Excel Generado", visible=False)
# Sección de resultados
with gr.Row():
with gr.Column(scale=1):
image_output = gr.Image(type="pil", label="🖼️ Factura con Entidades")
with gr.Column(scale=1):
filename_output = gr.Textbox(label="📄 Nombre de Archivo", interactive=False)
with gr.Row():
prev_button = gr.Button("⬅️ Anterior", interactive=False, size="lg")
save_button = gr.Button("💾 Guardar", variant="secondary", interactive=False, size="lg")
next_button = gr.Button("Siguiente ➡️", interactive=False, size="lg")
table_output = gr.Dataframe(
headers=["Etiqueta", "Valor"],
label="📋 Resultados de NER",
interactive=True,
col_count=(2, "fixed"),
datatype=["str", "str"]
)
# EVENTOS
# Procesar lote
process_button.click(
fn=self.batch_processor.process_batch,
inputs=[file_input],
outputs=[
master_dataframe_state, # DataFrame
current_index_state, # Índice inicial
status_output, # Estado
image_output, # Primera imagen
filename_output # Primer nombre
]
).then(
fn=lambda df, idx: self.navigator.get_row_as_table(df, idx),
inputs=[master_dataframe_state, current_index_state],
outputs=[table_output]
).then(
fn=self.enable_buttons,
inputs=[master_dataframe_state],
outputs=[prev_button, save_button, next_button, download_button]
)
# Guardar cambios
save_button.click(
fn=self.update_dataframe_with_edits,
inputs=[master_dataframe_state, current_index_state, table_output],
outputs=[master_dataframe_state]
).then(
fn=lambda idx: f"✓ Cambios guardados en factura {idx + 1}",
inputs=[current_index_state],
outputs=[status_output]
)
# Auto-guardar al editar
table_output.change(
fn=self.update_dataframe_with_edits,
inputs=[master_dataframe_state, current_index_state, table_output],
outputs=[master_dataframe_state]
)
# Navegación: Anterior
prev_button.click(
fn=lambda df, idx: self.navigator.go_prev(df, idx, self.batch_processor),
inputs=[master_dataframe_state, current_index_state],
outputs=[current_index_state, image_output, table_output, status_output, filename_output]
)
# Navegación: Siguiente
next_button.click(
fn=lambda df, idx: self.navigator.go_next(df, idx, self.batch_processor),
inputs=[master_dataframe_state, current_index_state],
outputs=[current_index_state, image_output, table_output, status_output, filename_output]
)
# Descargar Excel
download_button.click(
fn=self.verify_and_generate_excel,
inputs=[master_dataframe_state],
outputs=[excel_file, status_output]
).then(
fn=lambda: gr.update(visible=True),
outputs=[excel_file]
)
return demo