Spaces:
Build error
Build error
| """ | |
| 🛒 Lista de Compras Inteligente con IA | |
| Aplicación web para gestionar listas de compras con clasificación automática | |
| usando inteligencia artificial. | |
| Autor: Edwin Villa | |
| Licencia: MIT | |
| """ | |
| import torch | |
| from sentence_transformers import SentenceTransformer | |
| import numpy as np | |
| from typing import List, Dict, Tuple | |
| import gradio as gr | |
| from datetime import datetime | |
| import pandas as pd | |
| import json | |
| # ========================= CLASIFICADOR ========================= | |
| class ProductClassifier: | |
| """Clasificador de productos usando embeddings semánticos""" | |
| def __init__(self): | |
| print("🔄 Cargando modelo de IA...") | |
| self.model = SentenceTransformer('hiiamsid/sentence_similarity_spanish_es') | |
| self.categories = { | |
| "Lácteos": [ | |
| "leche", "yogurt", "queso", "mantequilla", "crema de leche", | |
| "kumis", "arequipe", "leche condensada", "queso crema", "natilla" | |
| ], | |
| "Frutas y Verduras": [ | |
| "manzana", "banana", "naranja", "tomate", "lechuga", "zanahoria", | |
| "papa", "cebolla", "aguacate", "limón", "plátano", "yuca", "cilantro", | |
| "arveja", "habichuela", "brócoli", "espinaca", "fresa", "mango", "piña" | |
| ], | |
| "Cárnicos": [ | |
| "pollo", "carne de res", "cerdo", "pescado", "salchicha", "jamón", | |
| "chorizo", "pechuga", "costilla", "lomo", "atún", "salmón", "molida" | |
| ], | |
| "Harinas y Cereales": [ | |
| "pan", "arroz", "pasta", "avena", "cereal", "harina", "tortilla", | |
| "arepa", "galletas", "tostadas", "quinoa", "lentejas", "frijoles" | |
| ], | |
| "Aseo Personal": [ | |
| "jabón", "shampoo", "crema dental", "desodorante", "papel higiénico", | |
| "toallas sanitarias", "cuchillas de afeitar", "enjuague bucal", "cepillo" | |
| ], | |
| "Limpieza Hogar": [ | |
| "detergente", "suavizante", "cloro", "limpiador", "escoba", "trapeador", | |
| "esponja", "jabón de platos", "ambientador", "desinfectante" | |
| ], | |
| "Mascotas": [ | |
| "comida para perro", "comida para gato", "arena para gato", | |
| "snacks para mascotas", "shampoo para mascotas", "juguetes para mascotas" | |
| ], | |
| "Bebidas": [ | |
| "agua", "jugo", "gaseosa", "café", "té", "cerveza", "vino", | |
| "energizante", "agua con gas", "chocolate en polvo", "cola" | |
| ], | |
| "Panadería": [ | |
| "pan tajado", "pan integral", "croissant", "ponqué", "torta", | |
| "donas", "muffin", "pan dulce", "pandebono" | |
| ], | |
| "Snacks y Dulces": [ | |
| "papas fritas", "chocolatina", "caramelos", "chicles", "maní", | |
| "doritos", "galletas dulces", "helado", "gomitas", "cheetos" | |
| ] | |
| } | |
| # Pre-calcular embeddings de categorías | |
| self.category_embeddings = {} | |
| for category, products in self.categories.items(): | |
| embeddings = self.model.encode(products) | |
| self.category_embeddings[category] = np.mean(embeddings, axis=0) | |
| print("✅ Modelo cargado correctamente") | |
| def classify(self, product: str) -> str: | |
| """Clasifica un producto en una categoría""" | |
| product_lower = product.lower().strip() | |
| # Búsqueda exacta primero | |
| for category, products in self.categories.items(): | |
| for prod in products: | |
| if prod in product_lower or product_lower in prod: | |
| return category | |
| # Similaridad semántica | |
| product_embedding = self.model.encode([product_lower])[0] | |
| similarities = {} | |
| for category, cat_embedding in self.category_embeddings.items(): | |
| similarity = np.dot(product_embedding, cat_embedding) / ( | |
| np.linalg.norm(product_embedding) * np.linalg.norm(cat_embedding) | |
| ) | |
| similarities[category] = similarity | |
| best_category = max(similarities, key=similarities.get) | |
| if similarities[best_category] < 0.3: | |
| return "Otros" | |
| return best_category | |
| # ========================= APLICACIÓN ========================= | |
| class ShoppingListApp: | |
| """Aplicación principal de lista de compras""" | |
| def __init__(self): | |
| self.classifier = ProductClassifier() | |
| self.shopping_list = [] | |
| self.next_id = 0 | |
| def add_product(self, product_name: str, quantity: str): | |
| """Agrega un producto a la lista""" | |
| if not product_name.strip(): | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "⚠️ Por favor ingresa un producto" | |
| category = self.classifier.classify(product_name) | |
| self.shopping_list.append({ | |
| "ID": self.next_id, | |
| "Producto": product_name.strip(), | |
| "Cantidad": quantity.strip() if quantity.strip() else "1", | |
| "Categoría": category, | |
| "Comprado": False | |
| }) | |
| self.next_id += 1 | |
| message = f"✅ '{product_name}' agregado a {category}" | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), message | |
| def update_product(self, selected_data, new_product, new_quantity, new_category): | |
| """Actualiza un producto existente""" | |
| if selected_data is None or len(selected_data) == 0: | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "⚠️ Por favor selecciona un producto" | |
| product_id = selected_data.iloc[0]['ID'] | |
| for item in self.shopping_list: | |
| if item["ID"] == product_id: | |
| if new_product.strip(): | |
| item["Producto"] = new_product.strip() | |
| if new_quantity.strip(): | |
| item["Cantidad"] = new_quantity.strip() | |
| if new_category and new_category != "Sin cambiar": | |
| item["Categoría"] = new_category | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), f"✏️ Producto actualizado" | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "❌ Error al actualizar" | |
| def delete_selected(self, selected_data): | |
| """Elimina el producto seleccionado""" | |
| if selected_data is None or len(selected_data) == 0: | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "⚠️ Por favor selecciona un producto" | |
| product_id = selected_data.iloc[0]['ID'] | |
| product_name = selected_data.iloc[0]['Producto'] | |
| self.shopping_list = [item for item in self.shopping_list if item["ID"] != product_id] | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), f"🗑️ '{product_name}' eliminado" | |
| def toggle_purchased(self, selected_data): | |
| """Marca/desmarca como comprado""" | |
| if selected_data is None or len(selected_data) == 0: | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "⚠️ Por favor selecciona un producto" | |
| product_id = selected_data.iloc[0]['ID'] | |
| for item in self.shopping_list: | |
| if item["ID"] == product_id: | |
| item["Comprado"] = not item["Comprado"] | |
| status = "comprado" if item["Comprado"] else "pendiente" | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), f"✓ Producto marcado como {status}" | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "" | |
| def clear_list(self): | |
| """Limpia toda la lista""" | |
| self.shopping_list = [] | |
| return self.get_dataframe(), self.get_filtered_dataframe(""), self.get_list_display(), "🗑️ Lista limpiada" | |
| def filter_by_category(self, category_filter): | |
| """Filtra productos por categoría""" | |
| return self.get_filtered_dataframe(category_filter) | |
| def search_products(self, search_term): | |
| """Busca productos por nombre""" | |
| if not search_term.strip(): | |
| return self.get_dataframe() | |
| search_lower = search_term.lower().strip() | |
| filtered = [item for item in self.shopping_list | |
| if search_lower in item["Producto"].lower()] | |
| if not filtered: | |
| return pd.DataFrame(columns=["ID", "Producto", "Cantidad", "Categoría", "Comprado"]) | |
| df = pd.DataFrame(filtered) | |
| df['Comprado'] = df['Comprado'].apply(lambda x: '✓' if x else '○') | |
| return df | |
| def get_dataframe(self): | |
| """Retorna DataFrame completo""" | |
| if not self.shopping_list: | |
| return pd.DataFrame(columns=["ID", "Producto", "Cantidad", "Categoría", "Comprado"]) | |
| df = pd.DataFrame(self.shopping_list) | |
| df['Comprado'] = df['Comprado'].apply(lambda x: '✓' if x else '○') | |
| return df | |
| def get_filtered_dataframe(self, category_filter): | |
| """Retorna DataFrame filtrado por categoría""" | |
| if not self.shopping_list: | |
| return pd.DataFrame(columns=["ID", "Producto", "Cantidad", "Categoría", "Comprado"]) | |
| if category_filter == "Todas" or not category_filter: | |
| return self.get_dataframe() | |
| filtered = [item for item in self.shopping_list if item["Categoría"] == category_filter] | |
| if not filtered: | |
| return pd.DataFrame(columns=["ID", "Producto", "Cantidad", "Categoría", "Comprado"]) | |
| df = pd.DataFrame(filtered) | |
| df['Comprado'] = df['Comprado'].apply(lambda x: '✓' if x else '○') | |
| return df | |
| def get_list_display(self): | |
| """Genera HTML de vista previa organizada""" | |
| if not self.shopping_list: | |
| return "<div style='text-align: center; padding: 40px; color: #666;'><h3>📝 Tu lista está vacía</h3><p>Comienza agregando productos</p></div>" | |
| categories = {} | |
| for item in self.shopping_list: | |
| cat = item["Categoría"] | |
| if cat not in categories: | |
| categories[cat] = [] | |
| categories[cat].append(item) | |
| html = "<div style='font-family: Arial, sans-serif;'>" | |
| total_items = len(self.shopping_list) | |
| purchased_items = sum(1 for item in self.shopping_list if item["Comprado"]) | |
| progress = (purchased_items / total_items * 100) if total_items > 0 else 0 | |
| html += f""" | |
| <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px;'> | |
| <h2 style='margin: 0 0 10px 0;'>🛒 Mi Lista de Compras</h2> | |
| <div style='display: flex; justify-content: space-around; margin-top: 15px;'> | |
| <div> | |
| <div style='font-size: 24px; font-weight: bold;'>{total_items}</div> | |
| <div style='font-size: 12px;'>Productos</div> | |
| </div> | |
| <div> | |
| <div style='font-size: 24px; font-weight: bold;'>{purchased_items}</div> | |
| <div style='font-size: 12px;'>Comprados</div> | |
| </div> | |
| <div> | |
| <div style='font-size: 24px; font-weight: bold;'>{progress:.0f}%</div> | |
| <div style='font-size: 12px;'>Progreso</div> | |
| </div> | |
| </div> | |
| <div style='background: rgba(255,255,255,0.2); height: 10px; border-radius: 5px; margin-top: 15px;'> | |
| <div style='background: white; height: 100%; width: {progress}%; border-radius: 5px;'></div> | |
| </div> | |
| </div> | |
| """ | |
| category_colors = { | |
| "Lácteos": "#4A90E2", | |
| "Frutas y Verduras": "#7ED321", | |
| "Cárnicos": "#E24A4A", | |
| "Harinas y Cereales": "#F5A623", | |
| "Aseo Personal": "#BD10E0", | |
| "Limpieza Hogar": "#50E3C2", | |
| "Mascotas": "#B8E986", | |
| "Bebidas": "#4A90E2", | |
| "Panadería": "#F8E71C", | |
| "Snacks y Dulces": "#FF6B9D", | |
| "Otros": "#9013FE" | |
| } | |
| for category in sorted(categories.keys()): | |
| items = categories[category] | |
| color = category_colors.get(category, "#999") | |
| html += f""" | |
| <div style='margin-bottom: 20px; border: 2px solid {color}; border-radius: 10px; overflow: hidden;'> | |
| <div style='background: {color}; color: white; padding: 10px 15px; font-weight: bold;'> | |
| {category} ({len(items)}) | |
| </div> | |
| <div style='padding: 10px;'> | |
| """ | |
| for item in items: | |
| checked = "✓" if item["Comprado"] else "○" | |
| style = "text-decoration: line-through; opacity: 0.6;" if item["Comprado"] else "" | |
| bg_color = "#f0f0f0" if item["Comprado"] else "white" | |
| html += f""" | |
| <div style='background: {bg_color}; padding: 12px; margin: 8px 0; border-radius: 8px; | |
| border: 1px solid #e0e0e0; {style}'> | |
| <div style='display: flex; align-items: center; justify-content: space-between;'> | |
| <div style='display: flex; align-items: center; flex: 1;'> | |
| <span style='font-size: 24px; margin-right: 10px;'>{checked}</span> | |
| <div> | |
| <div style='font-weight: 500; font-size: 16px;'>{item["Producto"]}</div> | |
| <div style='color: #666; font-size: 14px;'>Cantidad: {item["Cantidad"]}</div> | |
| </div> | |
| </div> | |
| <div style='color: #999; font-size: 12px; background: #eee; padding: 4px 8px; border-radius: 4px;'> | |
| ID: {item["ID"]} | |
| </div> | |
| </div> | |
| </div> | |
| """ | |
| html += "</div></div>" | |
| html += "</div>" | |
| return html | |
| def export_list(self): | |
| """Exporta la lista a formato texto""" | |
| if not self.shopping_list: | |
| return "La lista está vacía" | |
| text = f"🛒 LISTA DE COMPRAS - {datetime.now().strftime('%d/%m/%Y %H:%M')}\n" | |
| text += "=" * 50 + "\n\n" | |
| categories = {} | |
| for item in self.shopping_list: | |
| cat = item["Categoría"] | |
| if cat not in categories: | |
| categories[cat] = [] | |
| categories[cat].append(item) | |
| for category in sorted(categories.keys()): | |
| text += f"\n📦 {category.upper()}\n" | |
| text += "-" * 30 + "\n" | |
| for item in categories[category]: | |
| status = "✓" if item["Comprado"] else "○" | |
| text += f"{status} {item['Producto']} - Cantidad: {item['Cantidad']}\n" | |
| total = len(self.shopping_list) | |
| purchased = sum(1 for item in self.shopping_list if item["Comprado"]) | |
| text += f"\n\n📊 RESUMEN: {purchased}/{total} productos comprados ({purchased/total*100:.0f}%)\n" | |
| return text | |
| def save_list_json(self): | |
| """Guarda la lista en formato JSON""" | |
| if not self.shopping_list: | |
| return "La lista está vacía", "" | |
| data = { | |
| "fecha": datetime.now().strftime('%Y-%m-%d %H:%M:%S'), | |
| "productos": self.shopping_list | |
| } | |
| json_str = json.dumps(data, indent=2, ensure_ascii=False) | |
| filename = f"lista_compras_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" | |
| return f"✅ Lista guardada como {filename}", json_str | |
| def load_list_json(self, file_content): | |
| """Carga una lista desde JSON""" | |
| try: | |
| # Verificar si el contenido está vacío | |
| if not file_content or not file_content.strip(): | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), "⚠️ Por favor pega el contenido JSON primero") | |
| # Intentar parsear el JSON | |
| data = json.loads(file_content.strip()) | |
| # Verificar estructura | |
| if "productos" not in data: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), "❌ El JSON no tiene la estructura correcta (falta 'productos')") | |
| self.shopping_list = data["productos"] | |
| # Actualizar next_id | |
| if self.shopping_list: | |
| self.next_id = max(item["ID"] for item in self.shopping_list) + 1 | |
| else: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), "⚠️ El archivo JSON está vacío") | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), f"✅ Lista cargada con {len(self.shopping_list)} productos") | |
| except json.JSONDecodeError as e: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), f"❌ JSON inválido: {str(e)}") | |
| except KeyError as e: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), f"❌ Falta el campo: {str(e)}") | |
| except Exception as e: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), f"❌ Error al cargar: {str(e)}") | |
| def load_list_from_file(self, file_path): | |
| """Carga una lista desde un archivo JSON""" | |
| try: | |
| if file_path is None: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), "⚠️ Por favor selecciona un archivo primero") | |
| # Leer el archivo | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| file_content = f.read() | |
| # Usar la función de carga existente | |
| return self.load_list_json(file_content) | |
| except FileNotFoundError: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), "❌ Archivo no encontrado") | |
| except Exception as e: | |
| return (self.get_dataframe(), self.get_filtered_dataframe(""), | |
| self.get_list_display(), f"❌ Error al leer archivo: {str(e)}") | |
| # ========================= INTERFAZ GRADIO ========================= | |
| app = ShoppingListApp() | |
| category_options = ["Sin cambiar", "Lácteos", "Frutas y Verduras", "Cárnicos", | |
| "Harinas y Cereales", "Aseo Personal", "Limpieza Hogar", | |
| "Mascotas", "Bebidas", "Panadería", "Snacks y Dulces", "Otros"] | |
| filter_options = ["Todas"] + category_options[1:] | |
| with gr.Blocks(theme=gr.themes.Soft(), title="🛒 Lista de Compras Inteligente", | |
| css=".gradio-container {max-width: 1400px !important}") as demo: | |
| gr.Markdown(""" | |
| # 🛒 Lista de Compras Inteligente con IA | |
| ### Agrega productos, edítalos y organiza tu lista de compras con clasificación automática por IA | |
| """) | |
| with gr.Row(): | |
| # COLUMNA IZQUIERDA | |
| with gr.Column(scale=1): | |
| gr.Markdown("### ➕ Agregar Nuevo Producto") | |
| product_input = gr.Textbox( | |
| label="Producto", | |
| placeholder="Ej: leche, manzanas, detergente...", | |
| ) | |
| quantity_input = gr.Textbox( | |
| label="Cantidad", | |
| placeholder="Ej: 2, 1kg, 500g...", | |
| value="1" | |
| ) | |
| add_btn = gr.Button("➕ Agregar a la Lista", variant="primary", size="lg") | |
| status_msg = gr.Textbox(label="Estado", interactive=False, show_label=False) | |
| gr.Markdown("---") | |
| gr.Markdown("### 🔍 Buscar y Filtrar") | |
| search_box = gr.Textbox( | |
| label="Buscar producto", | |
| placeholder="Escribe para buscar..." | |
| ) | |
| category_filter = gr.Dropdown( | |
| choices=filter_options, | |
| value="Todas", | |
| label="Filtrar por categoría" | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### 📋 Productos") | |
| gr.Markdown("*Haz clic en una fila para seleccionarla*") | |
| products_table = gr.Dataframe( | |
| value=app.get_dataframe(), | |
| label="Lista de Productos", | |
| interactive=False, | |
| row_count=10, | |
| col_count=(5, "fixed"), | |
| wrap=True | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### ✏️ Editar Producto Seleccionado") | |
| with gr.Row(): | |
| edit_product = gr.Textbox( | |
| label="Nuevo nombre", | |
| placeholder="Dejar vacío para no cambiar" | |
| ) | |
| edit_quantity = gr.Textbox( | |
| label="Nueva cantidad", | |
| placeholder="Dejar vacío para no cambiar" | |
| ) | |
| edit_category = gr.Dropdown( | |
| choices=category_options, | |
| value="Sin cambiar", | |
| label="Nueva categoría" | |
| ) | |
| with gr.Row(): | |
| update_btn = gr.Button("💾 Guardar Cambios", variant="primary") | |
| toggle_btn = gr.Button("✓ Marcar/Desmarcar") | |
| with gr.Row(): | |
| delete_btn = gr.Button("🗑️ Eliminar", variant="stop") | |
| clear_btn = gr.Button("🗑️ Limpiar Todo", variant="stop") | |
| # COLUMNA DERECHA | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 👁️ Vista Previa por Categorías") | |
| list_display = gr.HTML(value=app.get_list_display()) | |
| gr.Markdown("---") | |
| gr.Markdown("### 💾 Guardar y Exportar") | |
| with gr.Row(): | |
| export_btn = gr.Button("📥 Exportar como Texto", variant="secondary") | |
| save_json_btn = gr.Button("💾 Guardar como JSON", variant="secondary") | |
| export_output = gr.Textbox(label="Salida", lines=12, visible=False) | |
| gr.Markdown("### 📂 Cargar Lista Guardada") | |
| gr.Markdown("**Opción 1: Pegar JSON**") | |
| load_file = gr.Textbox( | |
| label="Contenido JSON", | |
| placeholder='Pega aquí el JSON completo que guardaste, ejemplo:\n{\n "fecha": "2024-12-13 10:30:00",\n "productos": [...]\n}', | |
| lines=5 | |
| ) | |
| load_btn = gr.Button("📂 Cargar desde Texto", variant="secondary") | |
| gr.Markdown("**Opción 2: Subir Archivo**") | |
| load_file_upload = gr.File( | |
| label="Subir archivo JSON", | |
| file_types=[".json"], | |
| type="filepath" | |
| ) | |
| load_file_btn = gr.Button("📂 Cargar desde Archivo", variant="secondary") | |
| # Event handlers | |
| add_btn.click( | |
| fn=app.add_product, | |
| inputs=[product_input, quantity_input], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ).then( | |
| fn=lambda: ("", "1"), | |
| outputs=[product_input, quantity_input] | |
| ) | |
| product_input.submit( | |
| fn=app.add_product, | |
| inputs=[product_input, quantity_input], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ).then( | |
| fn=lambda: ("", "1"), | |
| outputs=[product_input, quantity_input] | |
| ) | |
| search_box.change( | |
| fn=app.search_products, | |
| inputs=[search_box], | |
| outputs=[products_table] | |
| ) | |
| category_filter.change( | |
| fn=app.filter_by_category, | |
| inputs=[category_filter], | |
| outputs=[products_table] | |
| ) | |
| update_btn.click( | |
| fn=app.update_product, | |
| inputs=[products_table, edit_product, edit_quantity, edit_category], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ).then( | |
| fn=lambda: ("", "", "Sin cambiar"), | |
| outputs=[edit_product, edit_quantity, edit_category] | |
| ) | |
| toggle_btn.click( | |
| fn=app.toggle_purchased, | |
| inputs=[products_table], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ) | |
| delete_btn.click( | |
| fn=app.delete_selected, | |
| inputs=[products_table], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ) | |
| clear_btn.click( | |
| fn=app.clear_list, | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ) | |
| export_btn.click( | |
| fn=app.export_list, | |
| outputs=export_output | |
| ).then( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=export_output | |
| ) | |
| save_json_btn.click( | |
| fn=app.save_list_json, | |
| outputs=[status_msg, export_output] | |
| ).then( | |
| fn=lambda: gr.update(visible=True), | |
| outputs=export_output | |
| ) | |
| load_btn.click( | |
| fn=app.load_list_json, | |
| inputs=[load_file], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ) | |
| load_file_btn.click( | |
| fn=app.load_list_from_file, | |
| inputs=[load_file_upload], | |
| outputs=[products_table, products_table, list_display, status_msg] | |
| ) | |
| if __name__ == "__main__": | |
| print("🚀 Iniciando aplicación...") | |
| demo.launch() |