# UI.py import gradio as gr import numpy as np # Necesario para np.inf en los valores por defecto de los límites def create_interface(process_function_for_button): """ Crea la interfaz de usuario completa para el modelado de bioprocesos. Conecta el botón de submit a la 'process_function_for_button' proporcionada. """ # Usar un tema estándar de Gradio para mayor compatibilidad. # theme='gradio/soft' es una buena opción. Puedes cambiarlo si tienes un tema personalizado que funcione. with gr.Blocks(theme='gradio/soft') as demo: gr.Markdown("# Modelado de Bioprocesos con Ecuaciones Personalizadas y Análisis por IA") gr.Markdown( "Sube un archivo Excel con columnas 'Tiempo', 'Biomasa', 'Sustrato', 'Producto'. " "Ingresa tus propias ecuaciones (usando 't' para tiempo y 'X_val' para X(t) en ecuaciones de S/P), " "los nombres de sus parámetros, y sus límites. El sistema ajustará los modelos y un LLM analizará los resultados." ) with gr.Row(): with gr.Column(scale=2): gr.Markdown("### 1. Carga de Datos y Configuración General del Gráfico") file_input = gr.File(label="Subir archivo Excel (.xlsx)", file_types=[".xlsx"]) show_legend_ui = gr.Checkbox(label="Mostrar leyenda en gráficos", value=True) show_params_ui = gr.Checkbox(label="Mostrar parámetros ajustados en gráficos", value=True) legend_position_ui = gr.Dropdown( label="Posición de la leyenda", choices=['best', 'upper right', 'upper left', 'lower right', 'lower left', 'center left', 'center right', 'lower center', 'upper center', 'center'], value='best', # String value, esto está bien type="value" ) with gr.Column(scale=1): gr.Markdown("### 2. Conteo de Ecuaciones a Probar") gr.Markdown("Define cuántas ecuaciones diferentes quieres probar para cada componente (1 a 3).") # Usar floats para gr.Number, Gradio los maneja bien incluso con precision=0 biomass_eq_count_ui = gr.Number(label="Ecuaciones de Biomasa:", value=1.0, minimum=1.0, maximum=3.0, step=1.0, precision=0) substrate_eq_count_ui = gr.Number(label="Ecuaciones de Sustrato:", value=1.0, minimum=1.0, maximum=3.0, step=1.0, precision=0) product_eq_count_ui = gr.Number(label="Ecuaciones de Producto:", value=1.0, minimum=1.0, maximum=3.0, step=1.0, precision=0) # --- Sección de Biomasa --- with gr.Accordion("3. Definición de Modelos de Biomasa", open=True): gr.Markdown("Ingresa la ecuación (ej: `Xm*(1 - exp(-um*(t - t_lag)))`), los nombres de los parámetros (ej: `Xm, um, t_lag`), y los límites de los parámetros (ej: `(0, np.inf), (0, 5), (0, 100)`). Usa `t` como la variable de tiempo.") with gr.Row(): with gr.Column(): # Columna 1 para Biomasa (siempre visible) biomass_eq1_ui = gr.Textbox(label="Ecuación de Biomasa 1", value="Xm * (1 - exp(-um * (t - t_lag)))", lines=2) biomass_param1_ui = gr.Textbox(label="Parámetros Biomasa 1", value="Xm, um, t_lag") biomass_bound1_ui = gr.Textbox(label="Límites Biomasa 1", value="(0, np.inf), (0, np.inf), (0, np.inf)") # Columna 2 para Biomasa biomass_col2_container = gr.Column(visible=False) with biomass_col2_container: biomass_eq2_ui = gr.Textbox(label="Ecuación de Biomasa 2", value="X0 * exp(um * t)", lines=2) # Ejemplo: Crecimiento exponencial biomass_param2_ui = gr.Textbox(label="Parámetros Biomasa 2", value="X0, um") biomass_bound2_ui = gr.Textbox(label="Límites Biomasa 2", value="(0, np.inf), (0, np.inf)") # Columna 3 para Biomasa biomass_col3_container = gr.Column(visible=False) with biomass_col3_container: biomass_eq3_ui = gr.Textbox(label="Ecuación de Biomasa 3", lines=2, value="", placeholder="Ej: (X0 * Xm * exp(um * t)) / (Xm - X0 + X0 * exp(um * t))") # Logística biomass_param3_ui = gr.Textbox(label="Parámetros Biomasa 3", value="", placeholder="X0, Xm, um") biomass_bound3_ui = gr.Textbox(label="Límites Biomasa 3", value="", placeholder="(0,np.inf),(0,np.inf),(0,np.inf)") # --- Sección de Sustrato --- with gr.Accordion("4. Definición de Modelos de Sustrato", open=True): gr.Markdown("Para Sustrato/Producto, si tu ecuación depende de la concentración de biomasa X(t) calculada por un modelo de biomasa, usa `X_val` en tu ecuación. Ejemplo: `S0 - (X_val / YXS)`.") with gr.Row(): with gr.Column(): substrate_eq1_ui = gr.Textbox(label="Ecuación de Sustrato 1", value="S0 - (X_val / YXS) - mS * t", lines=2) substrate_param1_ui = gr.Textbox(label="Parámetros Sustrato 1", value="S0, YXS, mS") substrate_bound1_ui = gr.Textbox(label="Límites Sustrato 1", value="(0, np.inf), (1e-9, np.inf), (0, np.inf)") # YXS > 0 substrate_col2_container = gr.Column(visible=False) with substrate_col2_container: substrate_eq2_ui = gr.Textbox(label="Ecuación de Sustrato 2", lines=2, value="") substrate_param2_ui = gr.Textbox(label="Parámetros Sustrato 2", value="") substrate_bound2_ui = gr.Textbox(label="Límites Sustrato 2", value="") substrate_col3_container = gr.Column(visible=False) with substrate_col3_container: substrate_eq3_ui = gr.Textbox(label="Ecuación de Sustrato 3", lines=2, value="") substrate_param3_ui = gr.Textbox(label="Parámetros Sustrato 3", value="") substrate_bound3_ui = gr.Textbox(label="Límites Sustrato 3", value="") # --- Sección de Producto --- with gr.Accordion("5. Definición de Modelos de Producto", open=True): gr.Markdown("Similar a Sustrato, usa `X_val` para la dependencia de la biomasa X(t).") with gr.Row(): with gr.Column(): product_eq1_ui = gr.Textbox(label="Ecuación de Producto 1", value="P0 + YPX * X_val + mP * t", lines=2) product_param1_ui = gr.Textbox(label="Parámetros Producto 1", value="P0, YPX, mP") product_bound1_ui = gr.Textbox(label="Límites Producto 1", value="(0, np.inf), (0, np.inf), (0, np.inf)") product_col2_container = gr.Column(visible=False) with product_col2_container: product_eq2_ui = gr.Textbox(label="Ecuación de Producto 2", lines=2, value="") product_param2_ui = gr.Textbox(label="Parámetros Producto 2", value="") product_bound2_ui = gr.Textbox(label="Límites Producto 2", value="") product_col3_container = gr.Column(visible=False) with product_col3_container: product_eq3_ui = gr.Textbox(label="Ecuación de Producto 3", lines=2, value="") product_param3_ui = gr.Textbox(label="Parámetros Producto 3", value="") product_bound3_ui = gr.Textbox(label="Límites Producto 3", value="") # Lógica para mostrar/ocultar campos de ecuación dinámicamente def update_eq_visibility(count_value): try: # El valor de gr.Number puede ser None si el usuario lo borra if count_value is None: count = 0 # O 1, dependiendo de cómo quieras manejarlo. 0 ocultará todo. else: count = int(float(count_value)) # Convertir a float primero, luego a int except ValueError: count = 0 # Si la conversión falla, asumir 0 # gr.update es la forma canónica de actualizar propiedades return gr.update(visible=count >= 2), gr.update(visible=count >= 3) # Conectar eventos .change() de los contadores a la función de visibilidad biomass_eq_count_ui.change(fn=update_eq_visibility, inputs=biomass_eq_count_ui, outputs=[biomass_col2_container, biomass_col3_container]) substrate_eq_count_ui.change(fn=update_eq_visibility, inputs=substrate_eq_count_ui, outputs=[substrate_col2_container, substrate_col3_container]) product_eq_count_ui.change(fn=update_eq_visibility, inputs=product_eq_count_ui, outputs=[product_col2_container, product_col3_container]) # Botón de envío submit_button = gr.Button("Procesar y Analizar Modelos", variant="primary", scale=1, elem_id="submit_button_main") # Salidas gr.Markdown("## Resultados del Análisis y Modelado") with gr.Row(): # Para la imagen, type="pil" es bueno si tu función devuelve un objeto PIL.Image image_output = gr.Image(label="Gráfico Generado de Ajustes", type="pil", scale=2, show_download_button=True, height=750) with gr.Column(scale=3): analysis_output = gr.Markdown(label="Análisis del Modelo por IA") # Lista de todos los inputs para el botón de submit all_inputs_for_button = [ file_input, biomass_eq1_ui, biomass_eq2_ui, biomass_eq3_ui, biomass_param1_ui, biomass_param2_ui, biomass_param3_ui, biomass_bound1_ui, biomass_bound2_ui, biomass_bound3_ui, substrate_eq1_ui, substrate_eq2_ui, substrate_eq3_ui, substrate_param1_ui, substrate_param2_ui, substrate_param3_ui, substrate_bound1_ui, substrate_bound2_ui, substrate_bound3_ui, product_eq1_ui, product_eq2_ui, product_eq3_ui, product_param1_ui, product_param2_ui, product_param3_ui, product_bound1_ui, product_bound2_ui, product_bound3_ui, legend_position_ui, show_legend_ui, show_params_ui, biomass_eq_count_ui, substrate_eq_count_ui, product_eq_count_ui ] outputs_for_button = [image_output, analysis_output] # Conexión del botón DENTRO del contexto de Blocks submit_button.click( fn=process_function_for_button, # Usa la función pasada como argumento inputs=all_inputs_for_button, outputs=outputs_for_button, # api_name="process_data" # Opcional, para nombrar el endpoint de la API ) # Inicializar visibilidad usando demo.load para que se aplique al cargar la UI def set_initial_visibility_on_load_wrapper(b_c_val, s_c_val, p_c_val): # Obtener los valores iniciales de los gr.Number components y aplicar la lógica de visibilidad. # Los valores de los Number inputs pueden ser float, convertirlos a int. b_c_int = int(float(b_c_val)) if b_c_val is not None else 0 s_c_int = int(float(s_c_val)) if s_c_val is not None else 0 p_c_int = int(float(p_c_val)) if p_c_val is not None else 0 b_vis2_upd, b_vis3_upd = update_eq_visibility(b_c_int) s_vis2_upd, s_vis3_upd = update_eq_visibility(s_c_int) p_vis2_upd, p_vis3_upd = update_eq_visibility(p_c_int) # Devolver los resultados de gr.update para cada componente de salida del demo.load return b_vis2_upd, b_vis3_upd, s_vis2_upd, s_vis3_upd, p_vis2_upd, p_vis3_upd demo.load( fn=set_initial_visibility_on_load_wrapper, inputs=[biomass_eq_count_ui, substrate_eq_count_ui, product_eq_count_ui], outputs=[ biomass_col2_container, biomass_col3_container, substrate_col2_container, substrate_col3_container, product_col2_container, product_col3_container ] ) return demo