import numpy as np import pandas as pd import statsmodels.formula.api as smf import statsmodels.api as sm import plotly.graph_objects as go from scipy.optimize import minimize import plotly.express as px from scipy.stats import t, f import gradio as gr import io import zipfile import tempfile from datetime import datetime import docx from docx.shared import Inches, Pt from docx.enum.text import WD_PARAGRAPH_ALIGNMENT import os # --- Global output components --- model_completo_output = gr.HTML() pareto_completo_output = gr.Plot() model_simplificado_output = gr.HTML() pareto_simplificado_output = gr.Plot() equation_output = gr.HTML() optimization_table_output = gr.Dataframe(label="Tabla de Optimización", interactive=False) prediction_table_output = gr.Dataframe(label="Tabla de Predicciones", interactive=False) contribution_table_output = gr.Dataframe(label="Tabla de % de Contribución", interactive=False) anova_table_output = gr.Dataframe(label="Tabla ANOVA Detallada", interactive=False) download_all_plots_button = gr.DownloadButton("Descargar Todos los Gráficos (ZIP)") download_excel_button = gr.DownloadButton("Descargar Tablas en Excel") rsm_plot_output = gr.Plot() plot_info = gr.Textbox(label="Información del Gráfico", value="Gráfico 1 de 9", interactive=False) current_index_state = gr.State(0) all_figures_state = gr.State([]) current_model_type_state = gr.State('simplified') model_personalized_output = gr.HTML() pareto_personalized_output = gr.Plot() factor_checkboxes = gr.CheckboxGroup(["factors", "x1_sq", "x2_sq", "x3_sq"], label="Términos de Factores", value=["factors", "x1_sq", "x2_sq", "x3_sq"]) interaction_checkboxes = gr.CheckboxGroup(["x1x2", "x1x3", "x2x3"], label="Términos de Interacción") # --- Clase RSM_BoxBehnken --- class RSM_BoxBehnken: def __init__(self, data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels): """ Inicializa la clase con los datos del diseño Box-Behnken. """ self.data = data.copy() self.model = None self.model_simplified = None self.model_personalized = None # For personalized model self.optimized_results = None self.optimal_levels = None self.all_figures_full = [] # Separate lists for different model plots self.all_figures_simplified = [] self.all_figures_personalized = [] self.x1_name = x1_name self.x2_name = x2_name self.x3_name = x3_name self.y_name = y_name # Niveles originales de las variables self.x1_levels = x1_levels self.x2_levels = x2_levels self.x3_levels = x3_levels def get_levels(self, variable_name): """ Obtiene los niveles para una variable específica. """ levels = {self.x1_name: self.x1_levels, self.x2_name: self.x2_levels, self.x3_name: self.x3_levels} return levels.get(variable_name) def fit_model(self): """ Ajusta el modelo de segundo orden completo a los datos. """ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + {self.x3_name} + ' \ f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2) + ' \ f'{self.x1_name}:{self.x2_name} + {self.x1_name}:{self.x3_name} + {self.x2_name}:{self.x3_name}' self.model = smf.ols(formula, data=self.data).fit() print("Modelo Completo:") print(self.model.summary()) return self.model, self.pareto_chart(self.model, "Pareto - Modelo Completo") def fit_simplified_model(self): """ Ajusta el modelo de segundo orden a los datos, eliminando términos no significativos. """ formula = f'{self.y_name} ~ {self.x1_name} + {self.x2_name} + ' \ f'I({self.x1_name}**2) + I({self.x2_name}**2) + I({self.x3_name}**2)' # Adjusted formula to include x3^2 self.model_simplified = smf.ols(formula, data=self.data).fit() print("\nModelo Simplificado:") print(self.model_simplified.summary()) return self.model_simplified, self.pareto_chart(self.model_simplified, "Pareto - Modelo Simplificado") def optimize(self, method='Nelder-Mead'): """ Encuentra los niveles óptimos de los factores para maximizar la respuesta usando el modelo simplificado. """ if self.model_simplified is None: print("Error: Ajusta el modelo simplificado primero.") return def objective_function(x): return -self.model_simplified.predict(pd.DataFrame({ self.x1_name: [x[0]], self.x2_name: [x[1]], self.x3_name: [x[2]] })).values[0] bounds = [(-1, 1), (-1, 1), (-1, 1)] x0 = [0, 0, 0] self.optimized_results = minimize(objective_function, x0, method=method, bounds=bounds) self.optimal_levels = self.optimized_results.x # Convertir niveles óptimos de codificados a naturales optimal_levels_natural = [ self.coded_to_natural(self.optimal_levels[0], self.x1_name), self.coded_to_natural(self.optimal_levels[1], self.x2_name), self.coded_to_natural(self.optimal_levels[2], self.x3_name) ] # Crear la tabla de optimización optimization_table = pd.DataFrame({ 'Variable': [self.x1_name, self.x2_name, self.x3_name], 'Nivel Óptimo (Natural)': optimal_levels_natural, 'Nivel Óptimo (Codificado)': self.optimal_levels }) return optimization_table.round(3) # Redondear a 3 decimales def fit_personalized_model(self, formula): """ Ajusta un modelo personalizado de segundo orden a los datos, usando la formula dada. """ self.model_personalized = smf.ols(formula, data=self.data).fit() print("\nModelo Personalizado:") print(self.model_personalized.summary()) return self.model_personalized, self.pareto_chart(self.model_personalized, "Pareto - Modelo Personalizado") def generate_all_plots(self): """ Genera todas las gráficas de RSM para todos los modelos. """ if self.model_simplified is None: print("Error: Ajusta el modelo simplificado primero.") return self.all_figures_full = [] # Reset lists for each model type self.all_figures_simplified = [] self.all_figures_personalized = [] levels_to_plot_natural = { # Levels from data, as before self.x1_name: sorted(list(set(self.data[self.x1_name]))), self.x2_name: sorted(list(set(self.data[self.x2_name]))), self.x3_name: sorted(list(set(self.data[self.x3_name]))) } for fixed_variable in [self.x1_name, self.x2_name, self.x3_name]: for level in levels_to_plot_natural[fixed_variable]: fig_full = self.plot_rsm_individual(fixed_variable, level, model_type='full') # Pass model_type if fig_full is not None: self.all_figures_full.append(fig_full) fig_simplified = self.plot_rsm_individual(fixed_variable, level, model_type='simplified') # Pass model_type if fig_simplified is not None: self.all_figures_simplified.append(fig_simplified) if self.model_personalized is not None: # Generate personalized plots only if model exists fig_personalized = self.plot_rsm_individual(fixed_variable, level, model_type='personalized') # Pass model_type if fig_personalized is not None: self.all_figures_personalized.append(fig_personalized) def plot_rsm_individual(self, fixed_variable, fixed_level, model_type='simplified'): # Added model_type parameter """ Genera un gráfico de superficie de respuesta (RSM) individual para una configuración específica y modelo. """ model_to_use = self.model_simplified # Default to simplified model model_title_suffix = "(Modelo Simplificado)" if model_type == 'full': model_to_use = self.model model_title_suffix = "(Modelo Completo)" elif model_type == 'personalized': if self.model_personalized is None: print("Error: Modelo personalizado no ajustado.") return None model_to_use = self.model_personalized model_title_suffix = "(Modelo Personalizado)" if model_to_use is None: # Use model_to_use instead of self.model_simplified print(f"Error: Ajusta el modelo {model_type} primero.") # More informative error message return None # Determinar las variables que varían y sus niveles naturales varying_variables = [var for var in [self.x1_name, self.x2_name, self.x3_name] if var != fixed_variable] # Establecer los niveles naturales para las variables que varían x_natural_levels = self.get_levels(varying_variables[0]) y_natural_levels = self.get_levels(varying_variables[1]) # Crear una malla de puntos para las variables que varían (en unidades naturales) x_range_natural = np.linspace(x_natural_levels[0], x_natural_levels[-1], 100) y_range_natural = np.linspace(y_natural_levels[0], y_natural_levels[-1], 100) x_grid_natural, y_grid_natural = np.meshgrid(x_range_natural, y_range_natural) # Convertir la malla de variables naturales a codificadas x_grid_coded = self.natural_to_coded(x_grid_natural, varying_variables[0]) y_grid_coded = self.natural_to_coded(y_range_natural, varying_variables[1]) # Crear un DataFrame para la predicción con variables codificadas prediction_data = pd.DataFrame({ varying_variables[0]: x_grid_coded.flatten(), varying_variables[1]: y_grid_coded.flatten(), }) prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable) # Fijar la variable fija en el DataFrame de predicción fixed_var_levels = self.get_levels(fixed_variable) if len(fixed_var_levels) == 3: # Box-Behnken design levels prediction_data[fixed_variable] = self.natural_to_coded(fixed_level, fixed_variable) elif len(fixed_var_levels) > 0: # Use the closest level if not Box-Behnken closest_level_coded = self.natural_to_coded(min(fixed_var_levels, key=lambda x:abs(x-fixed_level)), fixed_variable) prediction_data[fixed_variable] = closest_level_coded # Calcular los valores predichos z_pred = model_to_use.predict(prediction_data).values.reshape(x_grid_coded.shape) # Use model_to_use here # Filtrar por el nivel de la variable fija (en codificado) fixed_level_coded = self.natural_to_coded(fixed_level, fixed_variable) subset_data = self.data[np.isclose(self.data[fixed_variable], fixed_level_coded)] # Filtrar por niveles válidos en las variables que varían valid_levels = [-1, 0, 1] experiments_data = subset_data[ subset_data[varying_variables[0]].isin(valid_levels) & subset_data[varying_variables[1]].isin(valid_levels) ] # Convertir coordenadas de experimentos a naturales experiments_x_natural = experiments_data[varying_variables[0]].apply(lambda x: self.coded_to_natural(x, varying_variables[0])) experiments_y_natural = experiments_data[varying_variables[1]].apply(lambda x: self.coded_to_natural(x, varying_variables[1])) # Crear el gráfico de superficie con variables naturales en los ejes y transparencia fig = go.Figure(data=[go.Surface(z=z_pred, x=x_grid_natural, y=y_grid_natural, colorscale='Viridis', opacity=0.7, showscale=True)]) # --- Añadir cuadrícula a la superficie --- # Líneas en la dirección x for i in range(x_grid_natural.shape[0]): fig.add_trace(go.Scatter3d( x=x_grid_natural[i, :], y=y_grid_natural[i, :], z=z_pred[i, :], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip' )) # Líneas en la dirección y for j in range(x_grid_natural.shape[1]): fig.add_trace(go.Scatter3d( x=x_grid_natural[:, j], y=y_grid_natural[:, j], z=z_pred[:, j], mode='lines', line=dict(color='gray', width=2), showlegend=False, hoverinfo='skip' )) # --- Fin de la adición de la cuadrícula --- # Añadir los puntos de los experimentos en la superficie de respuesta con diferentes colores y etiquetas colors = px.colors.qualitative.Safe point_labels = [f"{row[self.y_name]:.3f}" for _, row in experiments_data.iterrows()] fig.add_trace(go.Scatter3d( x=experiments_x_natural, y=experiments_y_natural, z=experiments_data[self.y_name].round(3), mode='markers+text', marker=dict(size=4, color=colors[:len(experiments_x_natural)]), text=point_labels, textposition='top center', name='Experimentos' )) # Añadir etiquetas y título con variables naturales fig.update_layout( scene=dict( xaxis_title=f"{varying_variables[0]} ({self.get_units(varying_variables[0])})", yaxis_title=f"{varying_variables[1]} ({self.get_units(varying_variables[1])})", zaxis_title=self.y_name, ), title=f"{self.y_name} vs {varying_variables[0]} y {varying_variables[1]}
{fixed_variable} fijo en {fixed_level:.3f} ({self.get_units(fixed_variable)}) {model_title_suffix}", # Updated title height=800, width=1000, showlegend=True ) return fig # --- Funciones para la Interfaz de Gradio --- def load_data(x1_name, x2_name, x3_name, y_name, x1_levels_str, x2_levels_str, x3_levels_str, data_str): try: x1_levels = [float(x.strip()) for x in x1_levels_str.split(',')] x2_levels = [float(x.strip()) for x in x2_levels_str.split(',')] x3_levels = [float(x.strip()) for x in x3_levels_str.split(',')] data_list = [row.split(',') for row in data_str.strip().split('\n')] column_names = ['Exp.', x1_name, x2_name, x3_name, y_name] data_loaded = pd.DataFrame(data_list, columns=column_names).apply(pd.to_numeric, errors='coerce') if not all(col in data_loaded.columns for col in column_names): raise ValueError("Data format incorrect.") global rsm, data data = data_loaded # Assign loaded data to global data variable rsm = RSM_BoxBehnken(data, x1_name, x2_name, x3_name, y_name, x1_levels, x2_levels, x3_levels) return data.round(3), gr.update(visible=True) except Exception as e: error_message = f"Error loading data: {str(e)}" print(error_message) return None, gr.update(visible=False) def fit_and_optimize_model(): if 'rsm' not in globals(): return [None]*11 model_completo, pareto_completo = rsm.fit_model() model_simplificado, pareto_simplificado = rsm.fit_simplified_model() optimization_table = rsm.optimize() equation = rsm.get_simplified_equation() prediction_table = rsm.generate_prediction_table() contribution_table = rsm.calculate_contribution_percentage() anova_table = rsm.calculate_detailed_anova() rsm.generate_all_plots() equation_formatted = equation.replace(" + ", "
+ ").replace(" ** ", "^").replace("*", " × ") equation_formatted = f"### Ecuación del Modelo Simplificado:
{equation_formatted}" excel_path = rsm.save_tables_to_excel() zip_path = rsm.save_figures_to_zip() return (model_completo_output, pareto_completo, model_simplificado_output, pareto_simplificado, equation_output, optimization_table, prediction_table, contribution_table, anova_table, zip_path, excel_path) def fit_custom_model(factor_checkboxes, interaction_checkboxes, model_personalized_output_component, pareto_personalized_output_component): if 'rsm' not in globals(): return [None]*2 formula_parts = [rsm.x1_name, rsm.x2_name, rsm.x3_name] if "factors" in factor_checkboxes else [] if "x1_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x1_name}**2)') if "x2_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x2_name}**2)') if "x3_sq" in factor_checkboxes: formula_parts.append(f'I({rsm.x3_name}**2)') if "x1x2" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x2_name}') if "x1x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x1_name}:{rsm.x3_name}') if "x2x3" in interaction_checkboxes: formula_parts.append(f'{rsm.x2_name}:{rsm.x3_name}') formula = f'{rsm.y_name} ~ ' + ' + '.join(formula_parts) if formula_parts else f'{rsm.y_name} ~ 1' custom_model, pareto_custom = rsm.fit_personalized_model(formula) rsm.generate_all_plots() return custom_model.summary().as_html(), pareto_custom def show_plot(current_index, all_figures, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None, f"No graphs for {model_type}.", current_index selected_fig = figure_list[current_index] plot_info_text = f"Gráfico {current_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})" return selected_fig, plot_info_text, current_index def navigate_plot(direction, current_index, all_figures, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None, f"No graphs for {model_type}.", current_index new_index = (current_index - 1) % len(figure_list) if direction == 'left' else (current_index + 1) % len(figure_list) selected_fig = figure_list[new_index] plot_info_text = f"Gráfico {new_index + 1} de {len(figure_list)} (Modelo {model_type.capitalize()})" return selected_fig, plot_info_text, current_index def download_current_plot(all_figures, current_index, model_type): figure_list = rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized if not figure_list: return None fig = figure_list[current_index] img_bytes = rsm.save_fig_to_bytes(fig) filename = f"Grafico_RSM_{model_type}_{current_index + 1}.png" with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as temp_file: temp_file.write(img_bytes) return temp_file.name def download_all_plots_zip(model_type): if 'rsm' not in globals(): return None if model_type == 'full': rsm.all_figures = rsm.all_figures_full elif model_type == 'simplified': rsm.all_figures = rsm.all_figures_simplified elif model_type == 'personalized': rsm.all_figures = rsm.all_figures_personalized zip_path = rsm.save_figures_to_zip() filename = f"Graficos_RSM_{model_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip" return zip_path def download_all_tables_excel(): if 'rsm' not in globals(): return None return rsm.save_tables_to_excel() def exportar_word(rsm_instance, tables_dict): return rsm_instance.export_tables_to_word(tables_dict) def create_gradio_interface(): global model_completo_output, pareto_completo_output, model_simplificado_output, pareto_simplificado_output, equation_output, optimization_table_output, prediction_table_output, contribution_table_output, anova_table_output, download_all_plots_button, download_excel_button, rsm_plot_output, plot_info, current_index_state, all_figures_state, current_model_type_state, model_personalized_output, pareto_personalized_output, factor_checkboxes, interaction_checkboxes with gr.Blocks() as demo: gr.Markdown("# Optimización de la Absorbancia usando RSM") with gr.Row(): with gr.Column(): gr.Markdown("## Configuración del Diseño") x1_name_input = gr.Textbox(label="Nombre de la Variable X1 (ej. Glucosa)", value="Glucosa_g_L") x2_name_input = gr.Textbox(label="Nombre de la Variable X2 (ej. Proteina_Pescado)", value="Proteina_Pescado_g_L") x3_name_input = gr.Textbox(label="Nombre de la Variable X3 (ej. Sulfato_Manganeso)", value="Sulfato_Manganeso_g_L") y_name_input = gr.Textbox(label="Nombre de la Variable Dependiente (ej. Absorbancia)", value="Abs_600nm") x1_levels_input = gr.Textbox(label="Niveles de X1 (separados por comas)", value="0, 5, 10") x2_levels_input = gr.Textbox(label="Niveles de X2 (separados por comas)", value="0, 1.4, 3.2, 5") x3_levels_input = gr.Textbox(label="Niveles de X3 (separados por comas)", value="0.25, 0.5, 0.75") data_input = gr.Textbox(label="Datos del Experimento (formato CSV)", lines=10, value="""Exp.,Glucosa_g_L,Proteina_Pescado_g_L,Sulfato_Manganeso_g_L,Abs_600nm 1,-1,-1,0,1.576 2,1,-1,0,1.474 3,-1,1,0,1.293 4,1,1,0,1.446 5,-1,0,-1,1.537 6,1,0,-1,1.415 7,-1,0,1,1.481 8,1,0,1,1.419 9,0,-1,-1,1.321 10,0,1,-1,1.224 11,0,-1,1,1.459 12,0,1,1,0.345 13,0,0,0,1.279 14,0,0,0,1.181 15,0,0,0,0.662, 16,-1,-1,0,1.760 17,1,-1,0,1.690 18,-1,1,0,1.485 19,1,1,0,1.658 20,-1,0,-1,1.728 21,1,0,-1,1.594 22,-1,0,1,1.673 23,1,0,1,1.607 24,0,-1,-1,1.531 25,0,1,-1,1.424 26,0,-1,1,1.595 27,0,1,1,0.344 28,0,0,0,1.477 29,0,0,0,1.257 30,0,0,0,0.660, 31,-1,-1,0,1.932 32,1,-1,0,1.780 33,-1,1,0,1.689 34,1,1,0,1.876 35,-1,0,-1,1.885 36,1,0,-1,1.824 37,-1,0,1,1.913 38,1,0,1,1.810 39,0,-1,-1,1.852 40,0,1,-1,1.694 41,0,1,1,1.831 42,0,1,1,0.347 43,0,0,0,1.752 44,0,0,0,1.367 45,0,0,0,0.656""") load_button = gr.Button("Cargar Datos") data_dropdown = gr.Dropdown(["All Data"], value="All Data", label="Seleccionar Datos") with gr.Column(): gr.Markdown("## Datos Cargados") data_output = gr.Dataframe(label="Tabla de Datos", interactive=False) with gr.Row(visible=False) as analysis_row: with gr.Column(): fit_button = gr.Button("Ajustar Modelo Simplificado y Completo") gr.Markdown("**Modelo Completo**") model_completo_output_comp = model_completo_output # Use global output_components pareto_completo_output_comp = pareto_completo_output gr.Markdown("**Modelo Simplificado**") model_simplificado_output_comp = model_simplificado_output pareto_simplificado_output_comp = pareto_simplificado_output gr.Markdown("## Modelo Personalizado") factor_checkboxes_comp = factor_checkboxes interaction_checkboxes_comp = interaction_checkboxes custom_model_button = gr.Button("Ajustar Modelo Personalizado") model_personalized_output_comp = model_personalized_output pareto_personalized_output_comp = pareto_personalized_output gr.Markdown("**Ecuación del Modelo Simplificado**") equation_output_comp = equation_output optimization_table_output_comp = optimization_table_output prediction_table_output_comp = prediction_table_output contribution_table_output_comp = contribution_table_output anova_table_output_comp = anova_table_output gr.Markdown("## Descargar Todas las Tablas") download_excel_button_comp = download_excel_button download_word_button = gr.DownloadButton("Descargar Tablas en Word") with gr.Column(): gr.Markdown("## Gráficos de Superficie de Respuesta") model_type_radio = gr.Radio(["simplified", "full", "personalized"], value="simplified", label="Tipo de Modelo para Gráficos") fixed_variable_input = gr.Dropdown(label="Variable Fija", choices=["Glucosa_g_L", "Proteina_Pescado_g_L", "Sulfato_Manganeso_g_L"], value="Glucosa_g_L") fixed_level_input = gr.Slider(label="Nivel de Variable Fija (Natural Units)", minimum=0, maximum=10, step=0.1, value=5.0) plot_button = gr.Button("Generar Gráficos") with gr.Row(): left_button = gr.Button("<") right_button = gr.Button(">") download_plot_button_comp = gr.DownloadButton("Descargar Gráfico Actual (PNG)") # Defined HERE download_all_plots_button_comp = gr.DownloadButton("Descargar Todos los Gráficos (ZIP)") rsm_plot_output_comp = rsm_plot_output plot_info_comp = plot_info current_index_state_comp = current_index_state all_figures_state_comp = all_figures_state current_model_type_state_comp = current_model_type_state load_button.click(load_data, inputs=[x1_name_input, x2_name_input, x3_name_input, y_name_input, x1_levels_input, x2_levels_input, x3_levels_input, data_input], outputs=[data_output, analysis_row]) fit_button.click(fit_and_optimize_model, inputs=[], outputs=[model_completo_output_comp, pareto_completo_output_comp, model_simplificado_output_comp, pareto_simplificado_output_comp, equation_output_comp, optimization_table_output_comp, prediction_table_output_comp, contribution_table_output_comp, anova_table_output_comp, download_all_plots_button_comp, download_excel_button_comp]) custom_model_button.click(fit_custom_model, inputs=[factor_checkboxes_comp, interaction_checkboxes_comp, model_personalized_output_comp, pareto_personalized_output_comp], outputs=[model_personalized_output_comp, pareto_personalized_output_comp]) # Pass output components as input and output plot_button.click(lambda fixed_var, fixed_lvl, model_type: show_plot(0, [], model_type) if not hasattr(rsm, 'all_figures_full') or not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'full' and not rsm.all_figures_full else show_plot(0, [], model_type) if model_type == 'simplified' and not rsm.all_figures_simplified else show_plot(0, [], model_type) if model_type == 'personalized' and not rsm.all_figures_personalized else show_plot(0, rsm.all_figures_full if model_type == 'full' else rsm.all_figures_simplified if model_type == 'simplified' else rsm.all_figures_personalized, model_type), inputs=[fixed_variable_input, fixed_level_input, model_type_radio], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp, current_model_type_state_comp]) left_button.click(lambda current_index, all_figures, model_type: navigate_plot('left', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp]) right_button.click(lambda current_index, all_figures, model_type: navigate_plot('right', current_index, all_figures, model_type), inputs=[current_index_state_comp, all_figures_state_comp, current_model_type_state_comp], outputs=[rsm_plot_output_comp, plot_info_comp, current_index_state_comp]) download_plot_button.click(download_current_plot, inputs=[all_figures_state_comp, current_index_state_comp, current_model_type_state_comp], outputs=download_plot_button_comp) download_all_plots_button.click(lambda model_type: download_all_plots_zip(model_type), inputs=[current_model_type_state_comp], outputs=download_all_plots_button_comp) download_excel_button.click(fn=lambda: download_all_tables_excel(), inputs=[], outputs=download_excel_button_comp) download_word_button.click(exportar_word, inputs=[gr.State(rsm), gr.State(rsm.get_all_tables())], outputs=download_word_button) # Pass rsm instance and tables as state return demo # --- Función Principal --- def main(): interface = create_gradio_interface() interface.launch(share=True) if __name__ == "__main__": main()