C2MV commited on
Commit
d1ce30b
ยท
verified ยท
1 Parent(s): fa429b6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +384 -798
app.py CHANGED
@@ -1,837 +1,423 @@
1
  import gradio as gr
2
- import PyPDF2
3
  import pandas as pd
4
- import numpy as np
5
- import io
6
- import os
7
  import json
8
- import zipfile
9
  import tempfile
10
- from typing import Dict, List, Tuple, Union, Optional, Generator
11
- import re
12
- from pathlib import Path
13
- import openpyxl
14
- from dataclasses import dataclass, asdict
15
- from enum import Enum
16
- from docx import Document
17
- from docx.shared import Inches, Pt, RGBColor
18
- from docx.enum.text import WD_ALIGN_PARAGRAPH
19
- from reportlab.lib import colors
20
- from reportlab.lib.pagesizes import letter, A4
21
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
22
- from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
23
- from reportlab.lib.units import inch
24
- from reportlab.pdfbase import pdfmetrics
25
- from reportlab.pdfbase.ttfonts import TTFont
26
- import matplotlib.pyplot as plt
27
  from datetime import datetime
28
- from openai import OpenAI
29
-
30
- # --- CONFIGURACIร“N Y CONSTANTES ---
31
- os.environ['GRADIO_ANALYTICS_ENABLED'] = 'False'
32
-
33
- client = OpenAI(
34
- base_url="https://api.studio.nebius.com/v1/",
35
- api_key=os.environ.get("NEBIUS_API_KEY")
36
- )
37
-
38
- # Se aรฑade la nueva etiqueta para el selector de columna
39
- TRANSLATIONS = {
40
- 'en': {
41
- 'title': '๐Ÿงฌ Scalable Biotech Model Analyzer',
42
- 'subtitle': 'Analyzes large sets of model fitting results using a chunking strategy',
43
- 'upload_files': '๐Ÿ“ Upload fitting results (CSV/Excel)',
44
- 'chunk_column_label': '๐Ÿ”ฌ Select Column for Grouping Experiments',
45
- 'chunk_column_info': 'Choose the column that identifies each unique experiment. This is used for chunking.',
46
- 'select_model': '๐Ÿค– IA Model (editable)',
47
- 'select_language': '๐ŸŒ Language',
48
- 'select_theme': '๐ŸŽจ Theme',
49
- 'detail_level': '๐Ÿ“‹ Analysis detail level',
50
- 'detailed': 'Detailed',
51
- 'summarized': 'Summarized',
52
- 'analyze_button': '๐Ÿš€ Analyze and Compare Models',
53
- 'export_format': '๐Ÿ“„ Export format',
54
- 'export_button': '๐Ÿ’พ Export Report',
55
- 'comparative_analysis': '๐Ÿ“Š Comparative Analysis',
56
- 'implementation_code': '๐Ÿ’ป Implementation Code',
57
- 'data_format': '๐Ÿ“‹ Expected data format',
58
- 'loading': 'Loading...',
59
- 'error_no_api': 'Please configure NEBIUS_API_KEY in HuggingFace Space secrets',
60
- 'error_no_files': 'Please upload fitting result files to analyze',
61
- 'report_exported': 'Report exported successfully as',
62
- 'additional_specs': '๐Ÿ“ Additional specifications for analysis',
63
- 'additional_specs_placeholder': 'Add any specific requirements or focus areas for the analysis...',
64
- 'output_tokens_per_chunk': '๐Ÿ”ข Max output tokens per chunk (1k-32k)',
65
- 'token_info': 'โ„น๏ธ Token usage information',
66
- 'input_token_count': 'Input tokens used',
67
- 'output_token_count': 'Output tokens used',
68
- 'total_token_count': 'Total tokens used',
69
- 'token_cost': 'Estimated cost',
70
- 'thinking_process': '๐Ÿง  Thinking Process',
71
- 'analysis_report': '๐Ÿ“Š Analysis Report',
72
- 'code_output': '๐Ÿ’ป Implementation Code',
73
- 'token_usage': '๐Ÿ’ฐ Token Usage'
74
- },
75
- 'es': {
76
- 'title': '๐Ÿงฌ Analizador Escalable de Modelos Biotecnolรณgicos',
77
- 'subtitle': 'Analiza grandes conjuntos de datos de ajuste de modelos usando una estrategia por partes',
78
- 'upload_files': '๐Ÿ“ Subir resultados de ajuste (CSV/Excel)',
79
- 'chunk_column_label': '๐Ÿ”ฌ Seleccionar Columna para Agrupar Experimentos',
80
- 'chunk_column_info': 'Elige la columna que identifica cada experimento รบnico. Se usarรก para dividir el anรกlisis.',
81
- 'select_model': '๐Ÿค– Modelo IA (editable)',
82
- 'select_language': '๐ŸŒ Idioma',
83
- 'select_theme': '๐ŸŽจ Tema',
84
- 'detail_level': '๐Ÿ“‹ Nivel de detalle del anรกlisis',
85
- 'detailed': 'Detallado',
86
- 'summarized': 'Resumido',
87
- 'analyze_button': '๐Ÿš€ Analizar y Comparar Modelos',
88
- 'export_format': '๐Ÿ“„ Formato de exportaciรณn',
89
- 'export_button': '๐Ÿ’พ Exportar Reporte',
90
- 'comparative_analysis': '๐Ÿ“Š Anรกlisis Comparativo',
91
- 'implementation_code': '๐Ÿ’ป Cรณdigo de Implementaciรณn',
92
- 'data_format': '๐Ÿ“‹ Formato de datos esperado',
93
- 'loading': 'Cargando...',
94
- 'error_no_api': 'Por favor configura NEBIUS_API_KEY en los secretos del Space',
95
- 'error_no_files': 'Por favor sube archivos con resultados de ajuste para analizar',
96
- 'report_exported': 'Reporte exportado exitosamente como',
97
- 'additional_specs': '๐Ÿ“ Especificaciones adicionales para el anรกlisis',
98
- 'additional_specs_placeholder': 'Agregue cualquier requerimiento especรญfico o รกreas de enfoque para el anรกlisis...',
99
- 'output_tokens_per_chunk': '๐Ÿ”ข Max tokens de salida por pieza (1k-32k)',
100
- 'token_info': 'โ„น๏ธ Informaciรณn de uso de tokens',
101
- 'input_token_count': 'Tokens de entrada usados',
102
- 'output_token_count': 'Tokens de salida usados',
103
- 'total_token_count': 'Total de tokens usados',
104
- 'token_cost': 'Costo estimado',
105
- 'thinking_process': '๐Ÿง  Proceso de Pensamiento',
106
- 'analysis_report': '๐Ÿ“Š Reporte de Anรกlisis',
107
- 'code_output': '๐Ÿ’ป Cรณdigo de Implementaciรณn',
108
- 'token_usage': '๐Ÿ’ฐ Uso de Tokens'
109
- }
110
- }
111
- THEMES = { 'light': gr.themes.Soft(), 'dark': gr.themes.Base() }
112
-
113
- QWEN_MODELS = {
114
- "Qwen/Qwen3-14B": {"max_context_tokens": 40960, "input_cost": 0.0000007, "output_cost": 0.0000021},
115
- "Qwen/Qwen3-7B": {"max_context_tokens": 40960, "input_cost": 0.00000035, "output_cost": 0.00000105},
116
- "Qwen/Qwen1.5-14B": {"max_context_tokens": 40960, "input_cost": 0.0000007, "output_cost": 0.0000021}
117
- }
118
-
119
- # --- CLASES DE UTILIDAD (Se asume que existen, omitidas por brevedad) ---
120
- class FileProcessor:
121
- """Clase para procesar diferentes tipos de archivos"""
122
-
123
- @staticmethod
124
- def extract_text_from_pdf(pdf_file) -> str:
125
- """Extrae texto de un archivo PDF"""
126
- try:
127
- pdf_reader = PyPDF2.PdfReader(io.BytesIO(pdf_file))
128
- text = ""
129
- for page in pdf_reader.pages:
130
- text += page.extract_text() + "\n"
131
- return text
132
- except Exception as e:
133
- return f"Error reading PDF: {str(e)}"
134
-
135
- @staticmethod
136
- def read_csv(csv_file) -> pd.DataFrame:
137
- """Lee archivo CSV"""
138
- try:
139
- return pd.read_csv(io.BytesIO(csv_file))
140
- except Exception as e:
141
- return None
142
-
143
- @staticmethod
144
- def read_excel(excel_file) -> pd.DataFrame:
145
- """Lee archivo Excel"""
146
- try:
147
- return pd.read_excel(io.BytesIO(excel_file))
148
- except Exception as e:
149
- return None
150
-
151
- @staticmethod
152
- def extract_from_zip(zip_file) -> List[Tuple[str, bytes]]:
153
- """Extrae archivos de un ZIP"""
154
- files = []
155
  try:
156
- with zipfile.ZipFile(io.BytesIO(zip_file), 'r') as zip_ref:
157
- for file_name in zip_ref.namelist():
158
- if not file_name.startswith('__MACOSX'):
159
- file_data = zip_ref.read(file_name)
160
- files.append((file_name, file_data))
161
- except Exception as e:
162
- print(f"Error processing ZIP: {e}")
163
- return files
164
-
165
- class ReportExporter:
166
- """Clase para exportar reportes a diferentes formatos"""
167
-
168
- @staticmethod
169
- def export_to_docx(content: str, filename: str, language: str = 'en') -> str:
170
- """Exporta el contenido a un archivo DOCX"""
171
- doc = Document()
172
-
173
- # Configurar estilos
174
- title_style = doc.styles['Title']
175
- title_style.font.size = Pt(24)
176
- title_style.font.bold = True
177
-
178
- heading_style = doc.styles['Heading 1']
179
- heading_style.font.size = Pt(18)
180
- heading_style.font.bold = True
181
-
182
- # Tรญtulo
183
- title_text = {
184
- 'en': 'Comparative Analysis Report - Biotechnological Models',
185
- 'es': 'Informe de Anรกlisis Comparativo - Modelos Biotecnolรณgicos',
186
- }
187
-
188
- doc.add_heading(title_text.get(language, title_text['en']), 0)
189
-
190
- # Fecha
191
- date_text = {
192
- 'en': 'Generated on',
193
- 'es': 'Generado el',
194
- }
195
- doc.add_paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
196
- doc.add_paragraph()
197
-
198
- # Procesar contenido
199
- lines = content.split('\n')
200
- current_paragraph = None
201
-
202
- for line in lines:
203
- line = line.strip()
204
-
205
- if line.startswith('###'):
206
- doc.add_heading(line.replace('###', '').strip(), level=2)
207
- elif line.startswith('##'):
208
- doc.add_heading(line.replace('##', '').strip(), level=1)
209
- elif line.startswith('#'):
210
- doc.add_heading(line.replace('#', '').strip(), level=0)
211
- elif line.startswith('**') and line.endswith('**'):
212
- # Texto en negrita
213
- p = doc.add_paragraph()
214
- run = p.add_run(line.replace('**', ''))
215
- run.bold = True
216
- elif line.startswith('- ') or line.startswith('* '):
217
- # Lista
218
- doc.add_paragraph(line[2:], style='List Bullet')
219
- elif line.startswith(tuple('0123456789')):
220
- # Lista numerada
221
- doc.add_paragraph(line, style='List Number')
222
- elif line == '---' or line.startswith('==='):
223
- # Separador
224
- doc.add_paragraph('_' * 50)
225
- elif line:
226
- # Pรกrrafo normal
227
- doc.add_paragraph(line)
228
-
229
- # Guardar documento
230
- doc.save(filename)
231
- return filename
232
-
233
- @staticmethod
234
- def export_to_pdf(content: str, filename: str, language: str = 'en') -> str:
235
- """Exporta el contenido a un archivo PDF"""
236
- # Crear documento PDF
237
- doc = SimpleDocTemplate(filename, pagesize=letter)
238
- story = []
239
- styles = getSampleStyleSheet()
240
-
241
- # Estilos personalizados
242
- title_style = ParagraphStyle(
243
- 'CustomTitle',
244
- parent=styles['Title'],
245
- fontSize=24,
246
- textColor=colors.HexColor('#1f4788'),
247
- spaceAfter=30
248
- )
249
-
250
- heading_style = ParagraphStyle(
251
- 'CustomHeading',
252
- parent=styles['Heading1'],
253
- fontSize=16,
254
- textColor=colors.HexColor('#2e5090'),
255
- spaceAfter=12
256
- )
257
-
258
- # Tรญtulo
259
- title_text = {
260
- 'en': 'Comparative Analysis Report - Biotechnological Models',
261
- 'es': 'Informe de Anรกlisis Comparativo - Modelos Biotecnolรณgicos',
262
- }
263
-
264
- story.append(Paragraph(title_text.get(language, title_text['en']), title_style))
265
-
266
- # Fecha
267
- date_text = {
268
- 'en': 'Generated on',
269
- 'es': 'Generado el',
270
- }
271
- story.append(Paragraph(f"{date_text.get(language, date_text['en'])}: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", styles['Normal']))
272
- story.append(Spacer(1, 0.5*inch))
273
-
274
- # Procesar contenido
275
- lines = content.split('\n')
276
-
277
- for line in lines:
278
- line = line.strip()
279
-
280
- if not line:
281
- story.append(Spacer(1, 0.2*inch))
282
- elif line.startswith('###'):
283
- story.append(Paragraph(line.replace('###', '').strip(), styles['Heading3']))
284
- elif line.startswith('##'):
285
- story.append(Paragraph(line.replace('##', '').strip(), styles['Heading2']))
286
- elif line.startswith('#'):
287
- story.append(Paragraph(line.replace('#', '').strip(), heading_style))
288
- elif line.startswith('**') and line.endswith('**'):
289
- text = line.replace('**', '')
290
- story.append(Paragraph(f"<b>{text}</b>", styles['Normal']))
291
- elif line.startswith('- ') or line.startswith('* '):
292
- story.append(Paragraph(f"โ€ข {line[2:]}", styles['Normal']))
293
- elif line == '---' or line.startswith('==='):
294
- story.append(Spacer(1, 0.3*inch))
295
- story.append(Paragraph("_" * 70, styles['Normal']))
296
- story.append(Spacer(1, 0.3*inch))
297
  else:
298
- # Limpiar caracteres especiales para PDF
299
- clean_line = line.replace('๐Ÿ“Š', '[GRAPH]').replace('๐ŸŽฏ', '[TARGET]').replace('๐Ÿ”', '[SEARCH]').replace('๐Ÿ’ก', '[TIP]')
300
- story.append(Paragraph(clean_line, styles['Normal']))
301
-
302
- # Construir PDF
303
- doc.build(story)
304
- return filename
305
-
306
- # --- CLASE AIAnalyzer (MODIFICADA PARA ACEPTAR chunk_column) ---
307
- class AIAnalyzer:
308
- """Clase para anรกlisis con IA que implementa una estrategia 'chunk-and-stitch'."""
309
- def __init__(self, client):
310
- self.client = client
311
- self.token_usage = {}
312
- self.reset_token_usage()
313
-
314
- def reset_token_usage(self):
315
- self.token_usage = {'input_tokens': 0, 'output_tokens': 0, 'total_tokens': 0, 'estimated_cost': 0.0}
316
-
317
- def _update_token_usage(self, model_name: str, usage):
318
- if not usage: return
319
- self.token_usage['input_tokens'] += usage.prompt_tokens
320
- self.token_usage['output_tokens'] += usage.completion_tokens
321
- self.token_usage['total_tokens'] += usage.total_tokens
322
- model_info = QWEN_MODELS.get(model_name, {})
323
- input_cost = model_info.get('input_cost', 0.0)
324
- output_cost = model_info.get('output_cost', 0.0)
325
- self.token_usage['estimated_cost'] += (usage.prompt_tokens * input_cost) + (usage.completion_tokens * output_cost)
326
 
327
- def _calculate_safe_max_tokens(self, model_name: str, user_requested_tokens: int) -> int:
328
- model_info = QWEN_MODELS.get(model_name, {"max_context_tokens": 32768})
329
- context_limit = model_info['max_context_tokens']
330
- PROMPT_SAFETY_MARGIN = 8192
331
- max_allowable_output = context_limit - PROMPT_SAFETY_MARGIN
332
- return max(100, min(user_requested_tokens, max_allowable_output))
333
- ####
334
- def _analyze_single_experiment(self, experiment_df: pd.DataFrame, experiment_id: str, qwen_model: str, lang_prefix: str, max_output_tokens: int) -> Optional[Dict]:
335
  """
336
- Analiza los resultados de un รบnico experimento (un 'chunk' de datos) y devuelve un JSON estructurado.
337
- Esta funciรณn es el nรบcleo de la estrategia 'map' en el enfoque 'map-reduce'.
 
 
 
338
  """
339
-
340
- # El prompt es la parte mรกs importante. Estรก diseรฑado para ser muy especรญfico y dar un ejemplo claro.
341
- prompt = f"""
342
- {lang_prefix}
343
- You are an expert biotechnological data analyst. Your task is to analyze the provided model fitting results for a single experiment identified as: '{experiment_id}'.
344
- The data contains different mathematical models that were fitted to experimental data for variables like Biomass, Substrate, or Product.
345
-
346
- DATA FOR THIS SPECIFIC EXPERIMENT ('{experiment_id}'):
347
- ```
348
- {experiment_df.to_string()}
349
- ```
350
-
351
- YOUR INSTRUCTIONS:
352
- 1. **Identify Best Models**: For EACH variable type present in the data (e.g., 'Biomass', 'Substrate'), determine the single best-performing model. The best model is the one with the highest Rยฒ value. If Rยฒ values are equal, use the lowest RMSE as a tie-breaker.
353
- 2. **Extract Key Information**: For each of these best models, you must extract:
354
- - The model's name.
355
- - The specific metrics (Rยฒ, RMSE, AIC, etc.) as key-value pairs.
356
- - All kinetic parameters and their fitted values (e.g., mu_max, Ks) as key-value pairs.
357
- 3. **Summarize All Tested Models**: Create a simple list of the names of ALL models that were tested in this experiment, regardless of their performance.
358
- 4. **Provide Biological Interpretation**: Write a brief, concise interpretation (2-3 sentences) of what the results for this specific experiment imply. For example, "The selection of the Monod model for biomass with a ยต_max of 0.45 suggests rapid growth under these conditions, while the high Rยฒ indicates a strong fit."
359
-
360
- **CRITICAL OUTPUT FORMAT**: You MUST respond ONLY with a single, valid JSON object. Do not add any explanatory text, markdown formatting, or anything else before or after the JSON structure.
361
-
362
- Follow this EXACT JSON structure:
363
- {{
364
- "experiment_id": "{experiment_id}",
365
- "best_models_by_variable": [
366
- {{
367
- "variable_type": "Biomass",
368
- "model_name": "Name of the best model for Biomass",
369
- "metrics": {{
370
- "R2": 0.99,
371
- "RMSE": 0.01,
372
- "AIC": -50.2
373
- }},
374
- "parameters": {{
375
- "mu_max": 0.5,
376
- "Ks": 10.2
377
- }}
378
- }},
379
- {{
380
- "variable_type": "Substrate",
381
- "model_name": "Name of the best model for Substrate",
382
- "metrics": {{
383
- "R2": 0.98,
384
- "RMSE": 0.05
385
- }},
386
- "parameters": {{
387
- "k_consumption": 1.5
388
- }}
389
- }}
390
- ],
391
- "all_tested_models": ["Monod", "Logistic", "Gompertz", "First_Order"],
392
- "interpretation": "A brief, data-driven interpretation of the kinetic behavior observed in this specific experiment."
393
- }}
394
- """
395
-
396
  try:
397
- # Calcular un nรบmero seguro de tokens de salida para evitar exceder el lรญmite de contexto del modelo.
398
- safe_max_tokens = self._calculate_safe_max_tokens(qwen_model, max_output_tokens)
399
-
400
- # Realizar la llamada a la API de OpenAI/Nebius
401
- response = self.client.chat.completions.create(
402
- model=qwen_model,
403
- max_tokens=safe_max_tokens,
404
- temperature=0.05, # Temperatura baja para una salida mรกs predecible y estructurada
405
- response_format={"type": "json_object"}, # Forza la salida a ser un JSON vรกlido
406
- messages=[
407
- {"role": "system", "content": "You are a helpful assistant designed to output JSON."},
408
- {"role": "user", "content": prompt}
409
- ]
410
- )
411
-
412
- # Actualizar el contador de tokens y el costo estimado.
413
- self._update_token_usage(qwen_model, response.usage)
414
-
415
- # Extraer el contenido de la respuesta.
416
- content = response.choices[0].message.content
417
-
418
- # Parsear la cadena de texto JSON a un diccionario de Python.
419
- # Este paso es propenso a errores si el LLM no sigue las instrucciones perfectamente.
420
- parsed_json = json.loads(content)
421
- return parsed_json
422
-
423
- except json.JSONDecodeError as e:
424
- # Capturar errores si la respuesta del modelo no es un JSON vรกlido.
425
- print(f"CRITICAL ERROR: Failed to decode JSON for experiment '{experiment_id}'.")
426
- print(f"JSONDecodeError: {e}")
427
- print(f"LLM Raw Output that caused the error:\n---\n{content}\n---")
428
- return None # Devolver None para indicar que el anรกlisis de este chunk fallรณ.
429
-
430
  except Exception as e:
431
- # Capturar otros errores de la API (ej. problemas de red, clave invรกlida, etc.).
432
- print(f"API Error during single analysis for experiment '{experiment_id}': {e}")
433
- return None # Devolver None para que el proceso principal pueda saltar este chunk.
434
 
435
- ####
436
- def _synthesize_comparative_analysis(self, individual_analyses: List[Dict], qwen_model: str, detail_level: str, lang_prefix: str, additional_specs: str, max_output_tokens: int) -> str:
437
- """
438
- Sintetiza los anรกlisis individuales (JSONs) en un reporte comparativo final en formato Markdown.
439
- Esta es la etapa 'reduce' del proceso.
440
- """
441
-
442
- # 1. Preparar los datos de entrada para el modelo.
443
- # Convertimos la lista de diccionarios de Python a una cadena de texto JSON bien formateada.
444
- # Esto es lo que el LLM verรก como su "base de conocimiento".
445
- analyses_summary = json.dumps(individual_analyses, indent=2)
446
-
447
- # 2. Construir el prompt de sรญntesis.
448
- # Este prompt es mรกs conceptual que el anterior. Le pide al modelo que actรบe como un cientรญfico senior.
449
-
450
- # Secciรณn para las especificaciones adicionales del usuario.
451
- user_specs_section = f"""
452
- ## User's Additional Specifications
453
- Please pay special attention to the following user-provided requirements during your analysis:
454
- - {additional_specs}
455
- """ if additional_specs else ""
456
-
457
- # Instrucciรณn de nivel de detalle basada en la selecciรณn del usuario.
458
- detail_instruction = (
459
- "Your report must be highly detailed and exhaustive. Include multiple tables, in-depth parameter comparisons, and nuanced biological interpretations."
460
- if detail_level == "detailed" else
461
- "Your report should be a high-level summary. Focus on the main conclusions and key takeaways, using concise tables and bullet points."
462
- )
463
-
464
- prompt = f"""
465
- {lang_prefix}
466
- You are a Principal Scientist tasked with creating a final, consolidated report from a series of individual experimental analyses.
467
- You have been provided with a JSON array, where each object represents the detailed analysis of one specific experiment.
468
-
469
- {user_specs_section}
470
-
471
- YOUR PRIMARY OBJECTIVE:
472
- Synthesize all the provided information into a single, cohesive, and comparative analysis report. The report must be written in rich Markdown format.
473
- {detail_instruction}
474
-
475
- Your final report MUST contain the following sections:
476
-
477
- ### 1. Executive Summary & Experimental Inventory
478
- - Start with a brief paragraph summarizing the scope of the experiments analyzed.
479
- - Create a Markdown table that serves as an inventory of all experiments. The table should list each `experiment_id`, the `variable_type` (e.g., Biomass), and the `model_name` of the best-performing model for that variable.
480
-
481
- ### 2. In-Depth Comparative Analysis
482
- - **Model Performance Matrix:** This is the most critical part. Create a Markdown table that compares the performance of all major models across all experiments. Use Rยฒ as the primary metric. Rows should be model names, and columns should be experiment IDs. This allows for a direct visual comparison of which models are robust across different conditions.
483
- - **Parameter Trend Analysis:** Analyze how key kinetic parameters (e.g., `mu_max`, `Ks`, etc.) change across the different experimental conditions. Discuss any observable trends, correlations, or significant differences. For example: "We observed that `mu_max` consistently increased as temperature rose from Exp_A to Exp_C, suggesting a direct correlation in this range."
484
- - **Model Selection Justification:** Discuss why certain models performed better under specific conditions, referencing the biological interpretations from the input data.
485
-
486
- ### 3. Overall Recommendations & Conclusions
487
- - **Globally Recommended Models:** Based on the entire dataset, declare the best overall model for each primary variable type (Biomass, Substrate, etc.). Justify your choice based on consistent high performance and robustness across experiments.
488
- - **Condition-Specific Guidelines:** Provide actionable recommendations. For example, "For experiments conducted under high pH conditions (similar to 'Exp_C'), the 'Gompertz' model is strongly recommended due to its superior fit."
489
- - **Suggestions for Future Research:** Briefly suggest a few next steps or potential experiments to validate the findings or explore new hypotheses.
490
-
491
- ---
492
- **INPUT DATA: JSON ARRAY OF INDIVIDUAL ANALYSES**
493
- ```json
494
- {analyses_summary}
495
- ```
496
- ---
497
-
498
- Now, generate the complete, final Markdown report based on these instructions.
499
- """
500
-
501
  try:
502
- # Aumentamos el nรบmero de tokens de salida solicitados para la etapa de sรญntesis,
503
- # ya que el reporte final puede ser largo. Se multiplica por 2 como heurรญstica.
504
- safe_max_tokens = self._calculate_safe_max_tokens(qwen_model, max_output_tokens * 2)
505
-
506
- # Realizar la llamada a la API
507
- response = self.client.chat.completions.create(
508
- model=qwen_model,
509
- max_tokens=safe_max_tokens,
510
- temperature=0.2, # Una temperatura ligeramente mรกs alta que en el anรกlisis individual para permitir mรกs creatividad en la redacciรณn.
511
- messages=[
512
- {"role": "user", "content": prompt}
513
- ]
514
- )
515
-
516
- # Actualizar el uso de tokens y el costo.
517
- self._update_token_usage(qwen_model, response.usage)
518
-
519
- # Devolver el contenido del reporte generado.
520
- return response.choices[0].message.content
521
-
522
  except Exception as e:
523
- # Manejar cualquier error durante la llamada a la API de sรญntesis.
524
- error_message = f"CRITICAL ERROR: Failed during the final report synthesis stage. Details: {e}"
525
- print(error_message)
526
- return error_message
527
 
528
- # --- DENTRO DE LA CLASE AIAnalyzer ---
529
-
530
- def analyze_data(self, data: pd.DataFrame, chunk_column: str, qwen_model: str, detail_level: str, language: str, additional_specs: str, max_output_tokens: int) -> Generator[Union[str, Dict], None, None]:
531
- """
532
- Orquesta el anรกlisis completo como un generador, produciendo actualizaciones de estado.
533
- """
534
- self.reset_token_usage()
535
-
536
- if chunk_column not in data.columns:
537
- yield {"error": f"The selected chunking column '{chunk_column}' was not found in the data."}
538
- return
539
-
540
- unique_experiments = data[chunk_column].unique()
541
- yield f"Identified {len(unique_experiments)} groups to analyze using column '{chunk_column}': {list(unique_experiments)}"
542
-
543
- individual_results = []
544
- lang_prefix = "Please respond in English. " if language == 'en' else "Por favor responde en espaรฑol. "
545
-
546
- for i, exp_id in enumerate(unique_experiments):
547
- yield f"({i+1}/{len(unique_experiments)}) Analyzing group: '{str(exp_id)}'..."
548
- experiment_df = data[data[chunk_column] == exp_id]
549
-
550
- result = self._analyze_single_experiment(experiment_df, str(exp_id), qwen_model, lang_prefix, max_output_tokens)
551
-
552
- if result:
553
- individual_results.append(result)
554
- yield f"โœ… Analysis for '{str(exp_id)}' complete."
555
- else:
556
- yield f"โš ๏ธ Failed to analyze '{str(exp_id)}'. Skipping."
557
-
558
- if not individual_results:
559
- yield {"error": "Could not analyze any of the data groups. Please check data format and API status."}
560
- return
561
-
562
- yield "All groups analyzed. Synthesizing final comparative report..."
563
- final_analysis = self._synthesize_comparative_analysis(
564
- individual_results, qwen_model, detail_level, lang_prefix, additional_specs, max_output_tokens
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  )
566
- yield "โœ… Final report generated."
567
-
568
- yield "Generating implementation code..."
569
- code_result = "# Code generation is a placeholder in this version."
570
- yield "โœ… Code generated."
571
-
572
- # Al final, produce el diccionario de resultados completo.
573
- yield {
574
- "analisis_completo": final_analysis,
575
- "codigo_implementacion": code_result,
576
- }
577
 
578
- # --- FUNCIร“N DE PROCESAMIENTO PRINCIPAL ---
579
- # --- FUNCIร“N DE PROCESAMIENTO PRINCIPAL (fuera de cualquier clase) ---
580
-
581
- def process_files_and_analyze(files, chunk_column: str, qwen_model: str, detail_level: str, language: str, additional_specs: str, max_output_tokens: int):
582
- """
583
- Procesa archivos subidos y orquesta el anรกlisis, actualizando la UI con 'yield'.
584
- """
585
- if not files:
586
- yield "Please upload a file first.", "", "", ""
587
- return
588
- if not chunk_column:
589
- yield "Please upload a file and select a column for grouping before analyzing.", "", "", ""
590
- return
591
 
592
- # Inicializa las variables que se irรกn actualizando.
593
- thinking_log = ["### ๐Ÿš€ Starting Analysis\n"]
594
- analysis_result, code_result, token_report = "", "", ""
595
-
596
- # Funciรณn auxiliar para actualizar el log y hacer yield a la UI
597
- def update_log_and_yield(message):
598
- nonlocal thinking_log
599
- thinking_log.append(f"- {datetime.now().strftime('%H:%M:%S')}: {message}\n")
600
- return "\n".join(thinking_log), gr.update(), gr.update(), gr.update()
601
-
602
- yield update_log_and_yield("Processing uploaded file...")
603
-
604
- file = files[0]
605
  try:
606
- df = pd.read_csv(file.name) if file.name.endswith('.csv') else pd.read_excel(file.name)
607
- yield update_log_and_yield(f"Successfully loaded data from '{Path(file.name).name}'.")
 
 
 
 
608
  except Exception as e:
609
- yield update_log_and_yield(f"Error reading file: {e}")
610
- return
611
-
612
- # Inicia el analizador
613
- analyzer = AIAnalyzer(client)
614
-
615
- # Itera sobre el generador `analyze_data`
616
- # Cada 'item' serรก una actualizaciรณn de estado (string) o el resultado final (dict)
617
- for item in analyzer.analyze_data(df, chunk_column, qwen_model, detail_level, language, additional_specs, max_output_tokens):
618
- if isinstance(item, str):
619
- # Es una actualizaciรณn de estado, actualizamos el log de "thinking"
620
- yield update_log_and_yield(item)
621
- elif isinstance(item, dict) and "error" in item:
622
- # Es un diccionario de error, terminamos el proceso.
623
- yield update_log_and_yield(f"ANALYSIS FAILED: {item['error']}")
624
- return
625
- elif isinstance(item, dict):
626
- # Es el diccionario de resultados final.
627
- analysis_result = item["analisis_completo"]
628
- code_result = item["codigo_implementacion"]
629
-
630
- # Almacenar en el estado global para la exportaciรณn
631
- app_state.current_analysis = analysis_result
632
- app_state.current_code = code_result
633
-
634
- # Formatear el reporte de tokens final
635
- t = TRANSLATIONS[language]
636
- token_info = analyzer.token_usage
637
- token_report = f"""
638
- ### {t['token_info']}
639
- - **{t['input_token_count']}:** {token_info['input_tokens']}
640
- - **{t['output_token_count']}:** {token_info['output_tokens']}
641
- - **{t['total_token_count']}:** {token_info['total_tokens']}
642
- - **{t['token_cost']}:** ${token_info['estimated_cost']:.6f}
643
- """
644
-
645
- # Hacemos un รบltimo yield con todos los resultados finales.
646
- yield "\n".join(thinking_log), analysis_result, code_result, token_report
647
 
648
- # --- ESTADO Y FUNCIONES DE UTILIDAD PARA LA UI ---
649
- class AppState:
650
- def __init__(self):
651
- self.current_analysis = ""
652
- self.current_code = ""
653
- self.current_language = "en"
654
 
655
- app_state = AppState()
656
- app = None
 
 
 
 
 
 
 
 
 
 
657
 
658
- def export_report(export_format: str, language: str) -> Tuple[str, Optional[str]]:
659
- """
660
- Exporta el reporte al formato seleccionado (DOCX o PDF) usando el estado global.
661
- Crea el archivo en un directorio temporal para evitar saturar el directorio de trabajo.
662
- """
663
- # 1. Verificar si hay contenido para exportar en el estado global.
664
- if not app_state.current_analysis:
665
- error_msg = TRANSLATIONS[language].get('error_no_files', 'No analysis available to export.')
666
- # Devuelve el mensaje de error y None para la ruta del archivo.
667
- return error_msg, None
668
-
669
- # 2. Generar un nombre de archivo รบnico con marca de tiempo.
670
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
671
 
672
- # 3. Crear un directorio temporal para almacenar el reporte.
673
- # Esto es una buena prรกctica para no llenar el directorio raรญz de la aplicaciรณn.
674
  try:
675
- temp_dir = tempfile.mkdtemp()
676
- except Exception as e:
677
- return f"Error creating temporary directory: {e}", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
- # 4. Construir la ruta completa del archivo y llamar al exportador correspondiente.
 
 
 
 
 
 
 
 
680
  try:
681
- if export_format == "DOCX":
682
- # Construye la ruta para el archivo .docx
683
- filename = os.path.join(temp_dir, f"biotech_analysis_report_{timestamp}.docx")
684
-
685
- # Llama al mรฉtodo estรกtico de la clase ReportExporter para crear el DOCX.
686
- # Se asume que ReportExporter estรก definido en otra parte del cรณdigo.
687
- ReportExporter.export_to_docx(
688
- content=app_state.current_analysis,
689
- filename=filename,
690
- language=language
691
- )
692
-
693
- elif export_format == "PDF":
694
- # Construye la ruta para el archivo .pdf
695
- filename = os.path.join(temp_dir, f"biotech_analysis_report_{timestamp}.pdf")
696
-
697
- # Llama al mรฉtodo estรกtico de la clase ReportExporter para crear el PDF.
698
- # Se asume que ReportExporter estรก definido en otra parte del cรณdigo.
699
- ReportExporter.export_to_pdf(
700
- content=app_state.current_analysis,
701
- filename=filename,
702
- language=language
703
- )
704
  else:
705
- # Manejar un caso improbable de formato no soportado.
706
- return f"Unsupported export format: {export_format}", None
707
-
708
- # 5. Si la creaciรณn del archivo fue exitosa, devolver un mensaje de รฉxito y la ruta al archivo.
709
- success_msg_template = TRANSLATIONS[language].get('report_exported', 'Report exported successfully as')
710
- success_msg = f"{success_msg_template} {os.path.basename(filename)}"
711
-
712
- return success_msg, filename
713
-
714
  except Exception as e:
715
- # 6. Si ocurre cualquier error durante la exportaciรณn (ej. falta de permisos, error en la librerรญa),
716
- # capturarlo y devolver un mensaje de error claro.
717
- error_message = f"Error during report export to {export_format}: {str(e)}"
718
- print(f"EXPORT ERROR: {error_message}") # Loguear el error en la consola para depuraciรณn.
719
- return error_message, None
720
 
721
- # --- INTERFAZ GRADIU COMPLETA ---
722
- def create_interface():
723
- global app
724
 
725
- def update_interface_language(language):
726
- app_state.current_language = language
727
- t = TRANSLATIONS[language]
728
- return [
729
- gr.update(value=f"# {t['title']}"), gr.update(value=t['subtitle']),
730
- gr.update(label=t['upload_files']), gr.update(label=t['chunk_column_label'], info=t['chunk_column_info']),
731
- gr.update(label=t['select_model']), gr.update(label=t['select_language']), gr.update(label=t['select_theme']),
732
- gr.update(label=t['detail_level']), gr.update(choices=[(t['detailed'], "detailed"), (t['summarized'], "summarized")]),
733
- gr.update(label=t['additional_specs'], placeholder=t['additional_specs_placeholder']),
734
- gr.update(label=t['output_tokens_per_chunk']), gr.update(value=t['analyze_button']),
735
- gr.update(label=t['export_format']), gr.update(value=t['export_button']),
736
- gr.update(label=t['thinking_process']), gr.update(label=t['analysis_report']),
737
- gr.update(label=t['code_output']), gr.update(label=t['token_usage']), gr.update(label=t['data_format'])
738
- ]
739
 
740
- with gr.Blocks(theme=THEMES['light'], title="Scalable Biotech Analyzer") as demo:
741
- with gr.Row():
742
- with gr.Column(scale=3):
743
- title_text = gr.Markdown(f"# {TRANSLATIONS['en']['title']}")
744
- subtitle_text = gr.Markdown(TRANSLATIONS['en']['subtitle'])
745
- with gr.Column(scale=1):
746
- language_selector = gr.Dropdown(choices=[("English", "en"), ("Espaรฑol", "es")], value="en", label="Language/Idioma")
747
- theme_selector = gr.Dropdown(choices=["light", "dark"], value="light", label="Theme/Tema")
748
-
749
- with gr.Row():
750
- with gr.Column(scale=1):
751
- files_input = gr.File(label=TRANSLATIONS['en']['upload_files'], file_count="multiple", type="filepath")
752
-
753
- # NUEVO COMPONENTE: Selector de columna de agrupaciรณn
754
- chunk_column_selector = gr.Dropdown(
755
- label=TRANSLATIONS['en']['chunk_column_label'],
756
- info=TRANSLATIONS['en']['chunk_column_info'],
757
- interactive=False # Se activa al subir archivo
758
- )
759
-
760
- model_selector = gr.Textbox(label=TRANSLATIONS['en']['select_model'], value="deepseek-ai/DeepSeek-V3-0324")
761
- detail_level_radio = gr.Radio(choices=[("Detailed", "detailed"), ("Summarized", "summarized")], value="detailed", label=TRANSLATIONS['en']['detail_level'])
762
- additional_specs = gr.Textbox(label=TRANSLATIONS['en']['additional_specs'], placeholder=TRANSLATIONS['en']['additional_specs_placeholder'], lines=3)
763
- output_tokens_slider = gr.Slider(minimum=1000, maximum=32000, value=4000, step=500, label=TRANSLATIONS['en']['output_tokens_per_chunk'])
764
-
765
- analyze_btn = gr.Button(TRANSLATIONS['en']['analyze_button'], variant="primary", interactive=False) # Desactivado por defecto
766
-
767
- gr.Markdown("---")
768
- export_format_radio = gr.Radio(choices=["DOCX", "PDF"], value="PDF", label=TRANSLATIONS['en']['export_format'])
769
- export_btn = gr.Button(TRANSLATIONS['en']['export_button'])
770
- export_status = gr.Textbox(label="Export Status", visible=False)
771
- export_file = gr.File(label="Download Report", visible=False)
772
-
773
- with gr.Column(scale=2):
774
- thinking_output = gr.Markdown(label=TRANSLATIONS['en']['thinking_process'])
775
- analysis_output = gr.Markdown(label=TRANSLATIONS['en']['analysis_report'])
776
- code_output = gr.Code(label=TRANSLATIONS['en']['code_output'], language="python")
777
- token_usage_output = gr.Markdown(label=TRANSLATIONS['en']['token_usage'])
778
 
779
- data_format_accordion = gr.Accordion(label=TRANSLATIONS['en']['data_format'], open=False)
780
- with data_format_accordion:
781
- gr.Markdown("""...""") # Contenido del acordeรณn sin cambios
782
 
783
- # --- Lร“GICA DE EVENTOS DE LA UI ---
784
-
785
- # NUEVO EVENTO: Se activa al subir un archivo para poblar el selector de columna
786
- def update_chunk_column_selector(files):
787
- if not files:
788
- return gr.update(choices=[], value=None, interactive=False), gr.update(interactive=False)
789
-
790
- try:
791
- file_path = files[0].name
792
- df = pd.read_csv(file_path, nrows=0) if file_path.endswith('.csv') else pd.read_excel(file_path, nrows=0)
793
- columns = df.columns.tolist()
794
-
795
- # Intenta encontrar una columna por defecto
796
- default_candidates = ['Experiment', 'Experimento', 'Condition', 'Run', 'Batch', 'ID']
797
- default_selection = next((col for col in default_candidates if col in columns), None)
798
-
799
- return gr.update(choices=columns, value=default_selection, interactive=True), gr.update(interactive=True)
800
- except Exception as e:
801
- gr.Warning(f"Could not read columns from file: {e}")
802
- return gr.update(choices=[], value=None, interactive=False), gr.update(interactive=False)
803
 
804
- files_input.upload(
805
- fn=update_chunk_column_selector,
806
- inputs=[files_input],
807
- outputs=[chunk_column_selector, analyze_btn]
808
- )
809
-
810
- analyze_btn.click(
811
- fn=process_files_and_analyze,
812
- inputs=[files_input, chunk_column_selector, model_selector, detail_level_radio, language_selector, additional_specs, output_tokens_slider],
813
- outputs=[thinking_output, analysis_output, code_output, token_usage_output]
814
- )
815
-
816
- # Eventos de idioma y exportaciรณn (sin cambios)
817
- language_selector.change(
818
- fn=update_interface_language,
819
- inputs=[language_selector],
820
- outputs=[title_text, subtitle_text, files_input, chunk_column_selector, model_selector, language_selector, theme_selector, detail_level_radio, detail_level_radio, additional_specs, output_tokens_slider, analyze_btn, export_format_radio, export_btn, thinking_output, analysis_output, code_output, token_usage_output, data_format_accordion]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
  )
822
- export_btn.click(fn=export_report, inputs=[export_format_radio, language_selector], outputs=[export_status, export_file])
823
-
824
- app = demo
825
- return demo
826
-
827
- # --- FUNCIร“N PRINCIPAL DE EJECUCIร“N ---
828
- def main():
829
- if not os.getenv("NEBIUS_API_KEY"):
830
- return gr.Interface(lambda: TRANSLATIONS['en']['error_no_api'], [], gr.Textbox(label="Configuration Error"))
831
- return create_interface()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
 
833
  if __name__ == "__main__":
834
- demo = main()
835
- if demo:
836
- print("===== Application Startup =====")
837
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, share=False, inbrowser=True)
 
1
  import gradio as gr
2
+ from gradio_client import Client, handle_file
3
  import pandas as pd
 
 
 
4
  import json
 
5
  import tempfile
6
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  from datetime import datetime
8
+ import plotly.graph_objects as go
9
+ import plotly.express as px
10
+ import numpy as np
11
+ from smolagents import CodeAgent, tool, InferenceClientModel
12
+ import logging
13
+
14
+ # Configuraciรณn de logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # --- INICIO DE CONFIGURACIร“N DE CLIENTES ---
19
+ # Se inicializan ambos clientes para el pipeline de dos etapas
20
+ try:
21
+ biotech_client = Client("C2MV/BiotechU4")
22
+ logger.info("โœ… Cliente BiotechU4 inicializado correctamente.")
23
+ except Exception as e:
24
+ logger.error(f"โŒ No se pudo inicializar el cliente de BiotechU4: {e}")
25
+ biotech_client = None
26
+
27
+ try:
28
+ analysis_client = Client("C2MV/Project-HF-2025-2")
29
+ logger.info("โœ… Cliente Project-HF-2025-2 inicializado correctamente.")
30
+ except Exception as e:
31
+ logger.error(f"โŒ No se pudo inicializar el cliente de Project-HF-2025-2: {e}")
32
+ analysis_client = None
33
+
34
+ # Configuraciรณn del motor de Hugging Face (opcional)
35
+ try:
36
+ hf_engine = InferenceClientModel(model_id="mistralai/Mistral-7B-Instruct-v0.2")
37
+ except Exception:
38
+ logger.warning("No se pudo inicializar el modelo de HF. Los agentes usarรกn lรณgica simple.")
39
+ hf_engine = None
40
+
41
+ # ============================================================================
42
+ # ๐Ÿค– SISTEMA DE AGENTES (Conservado y adaptado)
43
+ # ============================================================================
44
+ class BiotechAgentTools:
45
+ @tool
46
+ def analyze_data_characteristics(data_info: str) -> dict:
47
+ """
48
+ Analiza las caracterรญsticas de los datos biotecnolรณgicos subidos.
49
+ Args:
50
+ data_info (str): Informaciรณn sobre el archivo de datos incluyendo nombre, tipo y contenido
51
+ Returns:
52
+ dict: Diccionario con tipo de experimento, modelos recomendados, parรกmetros sugeridos y calidad de datos
53
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  try:
55
+ characteristics = {"experiment_type": "unknown", "recommended_models": [], "suggested_params": {}, "data_quality": "good"}
56
+ data_lower = data_info.lower()
57
+ models_from_docs = ['logistic', 'gompertz', 'moser', 'baranyi', 'monod', 'contois', 'andrews', 'tessier', 'richards', 'stannard', 'huang']
58
+ growth_models = [m for m in ['logistic', 'gompertz', 'baranyi', 'richards'] if m in models_from_docs]
59
+ fermentation_models = [m for m in ['monod', 'contois', 'andrews', 'moser'] if m in models_from_docs]
60
+ if "biomass" in data_lower or "growth" in data_lower:
61
+ characteristics.update({"experiment_type": "growth_kinetics", "recommended_models": growth_models, "suggested_params": {"component": "biomass", "use_de": True, "maxfev": 75000}})
62
+ elif "ferment" in data_lower or "substrate" in data_lower:
63
+ characteristics.update({"experiment_type": "fermentation", "recommended_models": fermentation_models,"suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  else:
65
+ characteristics.update({"experiment_type": "general_biotech", "recommended_models": growth_models, "suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}})
66
+ logger.info(f"Anรกlisis completado: {characteristics['experiment_type']}")
67
+ return characteristics
68
+ except Exception as e:
69
+ logger.error(f"Error en anรกlisis de datos: {str(e)}")
70
+ return {"experiment_type": "error", "recommended_models": ['logistic', 'gompertz'], "suggested_params": {"component": "all", "use_de": False, "maxfev": 50000}, "data_quality": "unknown"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ @tool
73
+ def prepare_ia_context(data_summary: str) -> str:
 
 
 
 
 
 
74
  """
75
+ Prepara el contexto especรญfico para el anรกlisis de IA.
76
+ Args:
77
+ data_summary (str): Resumen de los datos analizados incluyendo tipo de experimento y resultados
78
+ Returns:
79
+ str: Contexto enriquecido y estructurado para el anรกlisis de IA
80
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  try:
82
+ enhanced_context = f"""CONTEXTO BIOTECNOLร“GICO ESPECรFICO:
83
+ Resultados del modelado: {data_summary}
84
+ Por favor, enfรณcate en:
85
+ 1. Interpretaciรณn biolรณgica de los parรกmetros ajustados (ej. ฮผmax, Ks, Yx/s).
86
+ 2. Comparaciรณn de la bondad de ajuste entre modelos (Rยฒ, RMSE).
87
+ 3. Implicaciones prรกcticas para el proceso biotecnolรณgico.
88
+ 4. Recomendaciones para la optimizaciรณn del proceso basadas en los modelos.
89
+ 5. Identificaciรณn de posibles limitaciones o artefactos en los datos o modelos.
90
+ Incluye un anรกlisis estadรญstico riguroso y recomendaciones prรกcticas y accionables."""
91
+ logger.info("Contexto preparado para la IA")
92
+ return enhanced_context
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  except Exception as e:
94
+ logger.error(f"Error preparando contexto: {str(e)}")
95
+ return data_summary
 
96
 
97
+ class CoordinatorAgent:
98
+ def __init__(self):
99
+ self.agent = CodeAgent(tools=[BiotechAgentTools.analyze_data_characteristics, BiotechAgentTools.prepare_ia_context], model=hf_engine) if hf_engine else None
100
+ self.tools = BiotechAgentTools()
101
+ def analyze_and_optimize(self, file_info: str, current_config: dict) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  try:
103
+ logger.info("๐Ÿค– Agente Coordinador iniciando anรกlisis...")
104
+ characteristics = self.tools.analyze_data_characteristics(file_info)
105
+ optimized_config = current_config.copy()
106
+ if characteristics["experiment_type"] != "error":
107
+ # Optimiza parรกmetros para BiotechU4
108
+ optimized_config.update({
109
+ "models": characteristics["recommended_models"],
110
+ "component": characteristics["suggested_params"]["component"],
111
+ "use_de": characteristics["suggested_params"]["use_de"],
112
+ "maxfev": characteristics["suggested_params"]["maxfev"]
113
+ })
114
+ # Optimiza 'additional_specs' para Project-HF-2025-2
115
+ if characteristics["experiment_type"] == "growth_kinetics":
116
+ optimized_config["additional_specs"] = self.tools.prepare_ia_context("Anรกlisis de cinรฉtica de crecimiento.")
117
+ elif characteristics["experiment_type"] == "fermentation":
118
+ optimized_config["additional_specs"] = self.tools.prepare_ia_context("Anรกlisis de datos de fermentaciรณn.")
119
+ logger.info(f"โœ… Configuraciรณn optimizada para: {characteristics['experiment_type']}")
120
+ return {"config": optimized_config, "analysis": characteristics, "recommendations": f"Configuraciรณn optimizada para {characteristics['experiment_type']}"}
 
 
121
  except Exception as e:
122
+ logger.error(f"โŒ Error en Agente Coordinador: {str(e)}")
123
+ return {"config": current_config, "analysis": {"experiment_type": "error"}, "recommendations": f"Error en optimizaciรณn: {str(e)}"}
 
 
124
 
125
+ class BiotechAgentSystem:
126
+ def __init__(self):
127
+ self.coordinator = CoordinatorAgent()
128
+ logger.info("๐Ÿš€ Sistema de agentes inicializado")
129
+ def process_with_agents(self, file_info: str, user_config: dict) -> dict:
130
+ try:
131
+ coordination_result = self.coordinator.analyze_and_optimize(file_info, user_config)
132
+ return {"success": True, **coordination_result}
133
+ except Exception as e:
134
+ logger.error(f"โŒ Error en sistema de agentes: {str(e)}")
135
+ return {"success": False, "config": user_config, "analysis": {"experiment_type": "error"}, "recommendations": f"Error: {str(e)}"}
136
+
137
+ # ============================================================================
138
+ # โš™๏ธ FUNCIONES DEL PIPELINE
139
+ # ============================================================================
140
+ agent_system = BiotechAgentSystem()
141
+
142
+ def create_dummy_plot():
143
+ fig = go.Figure(go.Scatter(x=[], y=[]))
144
+ fig.update_layout(title="Esperando resultados...", template="plotly_white", height=500, annotations=[dict(text="Sube un archivo y ejecuta el pipeline para ver los resultados", showarrow=False)])
145
+ return fig
146
+
147
+ def parse_plot_data(plot_info):
148
+ """Parsea la informaciรณn de la grรกfica recibida de la API BiotechU4."""
149
+ if not plot_info:
150
+ return create_dummy_plot()
151
+ try:
152
+ if isinstance(plot_info, dict) and 'plot' in plot_info:
153
+ plot_json_string = plot_info['plot']
154
+ return go.Figure(json.loads(plot_json_string))
155
+ if isinstance(plot_info, str): return go.Figure(json.loads(plot_info))
156
+ if isinstance(plot_info, dict): return go.Figure(plot_info)
157
+ except Exception as e:
158
+ logger.error(f"Error parsing plot: {e}")
159
+ return create_dummy_plot()
160
+
161
+ # --- FUNCIร“N PRINCIPAL DEL PIPELINE COMBINADO ---
162
+ def process_complete_pipeline_with_agents(
163
+ # Entradas de la UI
164
+ file, models, component, use_de, maxfev, exp_names,
165
+ chunk_column, ia_model, detail_level, language, additional_specs, max_output_tokens,
166
+ export_format, use_personal_key, personal_api_key,
167
+ # Progreso
168
+ progress=gr.Progress()):
169
+
170
+ progress(0, desc="๐Ÿš€ Iniciando Pipeline...")
171
+ if not file:
172
+ return create_dummy_plot(), None, None, None, None, "โŒ Por favor, sube un archivo."
173
+ if not models:
174
+ return create_dummy_plot(), None, None, None, None, "โŒ Por favor, selecciona al menos un modelo para el anรกlisis."
175
+
176
+ progress_updates = []
177
+
178
+ # 1. Sistema de Agentes para optimizar la configuraciรณn
179
+ progress(0.1, desc="๐Ÿค– Activando sistema de agentes...")
180
+ file_info = f"Archivo: {os.path.basename(file.name)}, Modelos: {models}"
181
+ user_config = {
182
+ "models": models, "component": component, "use_de": use_de, "maxfev": maxfev,
183
+ "additional_specs": additional_specs
184
+ }
185
+ agent_result = agent_system.process_with_agents(file_info, user_config)
186
+
187
+ if agent_result["success"]:
188
+ optimized_config = agent_result["config"]
189
+ progress_updates.append(f"โœ… Agentes detectaron: {agent_result['analysis']['experiment_type']}")
190
+ progress_updates.append(f"๐ŸŽฏ {agent_result['recommendations']}")
191
+ # Sobrescribir parรกmetros con las optimizaciones del agente
192
+ models, component, use_de, maxfev, additional_specs = (
193
+ optimized_config.get("models", models),
194
+ optimized_config.get("component", component),
195
+ optimized_config.get("use_de", use_de),
196
+ optimized_config.get("maxfev", maxfev),
197
+ optimized_config.get("additional_specs", additional_specs)
198
  )
199
+ else:
200
+ progress_updates.append(f"โš ๏ธ Agentes no pudieron optimizar: {agent_result['recommendations']}")
 
 
 
 
 
 
 
 
 
201
 
202
+ # 2. Ejecutar anรกlisis biotecnolรณgico con BiotechU4
203
+ progress(0.2, desc="๐Ÿ”ฌ Ejecutando anรกlisis biotecnolรณgico...")
204
+ if not biotech_client:
205
+ return create_dummy_plot(), None, None, None, None, "\n".join(progress_updates) + "\nโŒ Error: El cliente de BiotechU4 no estรก disponible."
 
 
 
 
 
 
 
 
 
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  try:
208
+ plot_info, df_data, status = biotech_client.predict(
209
+ file=handle_file(file.name), models=models, component=component,
210
+ use_de=use_de, maxfev=maxfev, exp_names=exp_names,
211
+ api_name="/run_analysis_wrapper"
212
+ )
213
+ progress_updates.append(f"โœ… Anรกlisis BiotechU4 completado: {status}")
214
  except Exception as e:
215
+ error_msg = f"Error en anรกlisis biotecnolรณgico: {e}"
216
+ return create_dummy_plot(), None, None, None, None, "\n".join(progress_updates) + f"\nโŒ {error_msg}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
 
218
+ if "Error" in status or not df_data:
219
+ return parse_plot_data(plot_info), None, None, None, None, "\n".join(progress_updates) + f"\nโŒ {status}"
 
 
 
 
220
 
221
+ # 3. Crear archivo CSV temporal para hacer de puente a la siguiente API
222
+ progress(0.4, desc="๐ŸŒ‰ Creando puente de datos (CSV)...")
223
+ temp_csv_file = None
224
+ try:
225
+ df = pd.DataFrame(df_data['data'], columns=df_data['headers'])
226
+ with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', delete=False, encoding='utf-8') as temp_f:
227
+ df.to_csv(temp_f.name, index=False)
228
+ temp_csv_file = temp_f.name
229
+ progress_updates.append("โœ… Puente de datos creado exitosamente.")
230
+ except Exception as e:
231
+ error_msg = f"Error al crear el archivo intermedio: {e}"
232
+ return parse_plot_data(plot_info), df_data, None, None, None, "\n".join(progress_updates) + f"\nโŒ {error_msg}"
233
 
234
+ # 4. Generar informe con Project-HF-2025-2
235
+ progress(0.5, desc=f"๐Ÿค– Generando informe con {ia_model}...")
236
+ if not analysis_client:
237
+ os.remove(temp_csv_file)
238
+ return parse_plot_data(plot_info), df_data, None, None, None, "\n".join(progress_updates) + "\nโŒ Error: El cliente de anรกlisis no estรก disponible."
 
 
 
 
 
 
 
 
239
 
 
 
240
  try:
241
+ # Usar clave personal si se proporciona
242
+ current_analysis_client = analysis_client
243
+ if use_personal_key and personal_api_key:
244
+ current_analysis_client = Client("C2MV/Project-HF-2025-2", hf_token=personal_api_key)
245
+ progress_updates.append("๐Ÿ”‘ Usando clave API personal para el informe.")
246
+
247
+ # Actualizar dinรกmicamente el selector de columna antes de la llamada principal
248
+ if not chunk_column:
249
+ chunk_choices = current_analysis_client.predict(files=[handle_file(temp_csv_file)], api_name="/update_chunk_column_selector")
250
+ chunk_column = chunk_choices[0] if chunk_choices else 'Experiment' # Fallback
251
+ progress_updates.append(f"โœ… Columna de agrupaciรณn seleccionada automรกticamente: {chunk_column}")
252
+
253
+ # Llamada principal a la API de anรกlisis
254
+ thinking_process, analysis_report, implementation_code, token_usage = current_analysis_client.predict(
255
+ files=[handle_file(temp_csv_file)],
256
+ chunk_column=chunk_column,
257
+ qwen_model=ia_model,
258
+ detail_level=detail_level,
259
+ language=language,
260
+ additional_specs=additional_specs,
261
+ max_output_tokens=max_output_tokens,
262
+ api_name="/process_files_and_analyze"
263
+ )
264
+ progress_updates.append(f"โœ… Informe de IA generado. {token_usage}")
265
+ progress_updates.append(f"๐Ÿง  Proceso de Pensamiento: {thinking_process}")
266
 
267
+ except Exception as e:
268
+ error_msg = f"Error generando el informe de IA: {e}"
269
+ return parse_plot_data(plot_info), df_data, error_msg, None, None, "\n".join(progress_updates) + f"\nโŒ {error_msg}"
270
+ finally:
271
+ if temp_csv_file and os.path.exists(temp_csv_file):
272
+ os.remove(temp_csv_file) # Limpiar el archivo temporal
273
+
274
+ # 5. Exportar el informe final
275
+ progress(0.9, desc=f"๐Ÿ“„ Exportando informe en {export_format}...")
276
  try:
277
+ export_status, report_file = analysis_client.predict(
278
+ export_format=export_format,
279
+ language=language,
280
+ api_name="/export_report"
281
+ )
282
+ if report_file:
283
+ progress_updates.append(f"โœ… Informe exportado: {export_status}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  else:
285
+ progress_updates.append(f"โŒ Error al exportar: {export_status}")
 
 
 
 
 
 
 
 
286
  except Exception as e:
287
+ report_file = None
288
+ progress_updates.append(f"โŒ Error excepcional durante la exportaciรณn: {e}")
 
 
 
289
 
290
+ progress(1, desc="๐ŸŽ‰ Pipeline Completado")
 
 
291
 
292
+ return parse_plot_data(plot_info), df_data, analysis_report, implementation_code, report_file, "\n".join(progress_updates)
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
+ def create_example_videos():
295
+ # ... (cรณdigo sin cambios)
296
+ pass
297
+
298
+ # ============================================================================
299
+ # ๐Ÿ–ผ๏ธ INTERFAZ DE USUARIO CON GRADIO (Combinada y Organizada)
300
+ # ============================================================================
301
+ BIOTECH_MODELS = ['logistic', 'gompertz', 'moser', 'baranyi', 'monod', 'contois', 'andrews', 'tessier', 'richards', 'stannard', 'huang']
302
+ DEFAULT_BIOTECH_SELECTION = ['logistic', 'gompertz', 'moser', 'baranyi']
303
+ IA_MODELS = ["deepseek-ai/DeepSeek-V3-0324"]
304
+ DEFAULT_IA_MODEL = "deepseek-ai/DeepSeek-V3-0324"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ theme = gr.themes.Soft(primary_hue="blue", secondary_hue="indigo", neutral_hue="slate")
307
+ custom_css = ".file-upload { border: 2px dashed #3b82f6; } button.primary { background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%); }"
 
308
 
309
+ create_example_videos()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
+ with gr.Blocks(theme=theme, title="BioTech Analysis & Report Generator", css=custom_css) as demo:
312
+ gr.Markdown(
313
+ """
314
+ # ๐Ÿงฌ BioTech Analysis & Report Generator
315
+ ## **Full Pipeline: Biotech Modeling โ†’ AI Reporting**
316
+ *An intelligent pipeline that automates the analysis of bioprocess data, from kinetic modeling with `BiotechU4` to generating detailed reports with `Project-HF-2025-2`.*
317
+ """
318
+ )
319
+ # ... (El resto del Markdown y Videos se mantiene igual)
320
+ with gr.Accordion("๐Ÿค– How the AI Agents Work (Click to Expand)", open=True):
321
+ gr.Markdown(
322
+ """
323
+ ```text
324
+ [ ๐Ÿ‘ค USER INPUT: Data File & Initial Settings ]
325
+ โ”‚
326
+ โ–ผ
327
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
328
+ โ”‚ ๐Ÿค– Coordinator Agent โ”‚
329
+ โ”‚ โ€ข Analyzes experiment type (e.g., kinetics, ferment.). โ”‚
330
+ โ”‚ โ€ข Recommends optimal models and parameters for BiotechU4.โ”‚
331
+ โ”‚ โ€ข Prepares a rich context for the AI Report Generator. โ”‚
332
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
333
+ โ”‚
334
+ โ–ผ
335
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
336
+ โ”‚ โš™๏ธ BiotechU4 API โ”‚
337
+ โ”‚ โ€ข Performs kinetic modeling and statistical fitting. โ”‚
338
+ โ”‚ โ€ข Returns: Plot, Results Table, Status. โ”‚
339
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
340
+ โ”‚
341
+ โ–ผ
342
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
343
+ โ”‚ ๐ŸŒ‰ Data Bridge โ”‚
344
+ โ”‚ โ€ข Converts the results table into a temporary CSV file. โ”‚
345
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
346
+ โ”‚
347
+ โ–ผ
348
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
349
+ โ”‚ ๐Ÿง  Project-HF-2025-2 API โ”‚
350
+ โ”‚ โ€ข Analyzes the modeling results from the CSV file. โ”‚
351
+ โ”‚ โ€ข Generates: Detailed Report, Python Code, Token Usage. โ”‚
352
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€๏ฟฝ๏ฟฝ๏ฟฝโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
353
+ โ”‚
354
+ โ–ผ
355
+ [ ๐Ÿ“„ FINAL OUTPUT: Plot, Table, Report, Code & Download ]
356
+ ```
357
+ """
358
  )
359
+ with gr.Row():
360
+ with gr.Column(scale=1):
361
+ gr.Markdown("## ๐Ÿ“Š Configuration")
362
+ file_input = gr.File(label="๐Ÿ“ Data File (CSV/Excel)", file_types=[".csv", ".xlsx", ".xls"], elem_classes=["file-upload"])
363
+ gr.Examples(examples=[os.path.join("examples", "archivo.xlsx")], inputs=[file_input], label="Click an example to run")
364
+
365
+ with gr.Accordion("๐Ÿ”ฌ Step 1: Biotech Analysis Parameters (AI Optimized)", open=True):
366
+ models_input = gr.CheckboxGroup(choices=BIOTECH_MODELS, value=DEFAULT_BIOTECH_SELECTION, label="๐Ÿ“Š Models to Test")
367
+ component_input = gr.Dropdown(['all', 'biomass', 'substrate', 'product'], value='all', label="๐Ÿ“ˆ Component to Visualize")
368
+ exp_names_input = gr.Textbox(label="๐Ÿท๏ธ Experiment Names", value="Biotech Analysis")
369
+ use_de_input = gr.Checkbox(label="๐Ÿงฎ Use Differential Evolution", value=False)
370
+ maxfev_input = gr.Slider(label="๐Ÿ”„ Max Iterations", minimum=10000, maximum=100000, value=50000, step=1000)
371
+
372
+ with gr.Accordion("๐Ÿค– Step 2: AI Report Generation Parameters", open=True):
373
+ chunk_column_input = gr.Dropdown(label="๐Ÿ”ฌ Select Column for Grouping", info="This is based on the results from Step 1. Default is usually fine.", choices=["Experiment"], value="Experiment", interactive=True)
374
+ ia_model_input = gr.Dropdown(choices=IA_MODELS, value=DEFAULT_IA_MODEL, label="๐Ÿค– IA Model")
375
+ detail_level_input = gr.Radio(['detailed', 'summarized'], value='detailed', label="๐Ÿ“‹ Detail Level")
376
+ max_output_tokens_input = gr.Slider(minimum=1000, maximum=32000, value=4000, step=100, label="๐Ÿ”ข Max Output Tokens")
377
+ additional_specs_input = gr.Textbox(label="๐Ÿ“ Additional Specifications", placeholder="AI Agents will customize this...", lines=3, value="Provide a detailed analysis of the models, metrics, and practical recommendations.")
378
+
379
+ with gr.Accordion("โš™๏ธ Global & Export Settings", open=True):
380
+ language_input = gr.Dropdown(['en', 'es'], value='en', label="๐ŸŒ Language")
381
+ export_format_input = gr.Radio(['PDF', 'DOCX'], value='PDF', label="๐Ÿ“„ Export Format")
382
+ with gr.Accordion("๐Ÿ”‘ Personal API Key (Optional)", open=False):
383
+ use_personal_key_input = gr.Checkbox(label="Use Personal HF Token for AI Report", value=False)
384
+ personal_api_key_input = gr.Textbox(label="Personal HF Token", type="password", placeholder="Enter your token (hf_...)", visible=False)
385
+
386
+ process_btn = gr.Button("๐Ÿš€ Run Full Pipeline with AI Agents", variant="primary", size="lg")
387
+
388
+ with gr.Column(scale=2):
389
+ gr.Markdown("## ๐Ÿ“ˆ Results")
390
+ status_output = gr.Textbox(label="๐Ÿ“Š Process Status Log", lines=8, interactive=False)
391
+ with gr.Tabs():
392
+ with gr.TabItem("๐Ÿ“Š Visualization"):
393
+ plot_output = gr.Plot()
394
+ with gr.TabItem("๐Ÿ“‹ Modeling Results Table"):
395
+ table_output = gr.Dataframe()
396
+ with gr.TabItem("๐Ÿ“ AI Analysis Report"):
397
+ analysis_output = gr.Markdown()
398
+ with gr.TabItem("๐Ÿ’ป Implementation Code"):
399
+ code_output = gr.Code(language="python")
400
+ report_output = gr.File(label="๐Ÿ“ฅ Download Final Report", interactive=False)
401
+
402
+ def toggle_api_key_visibility(checked):
403
+ return gr.Textbox(visible=checked)
404
+
405
+ use_personal_key_input.change(fn=toggle_api_key_visibility, inputs=use_personal_key_input, outputs=personal_api_key_input)
406
+
407
+ process_btn.click(
408
+ fn=process_complete_pipeline_with_agents,
409
+ inputs=[
410
+ file_input, models_input, component_input, use_de_input, maxfev_input, exp_names_input,
411
+ chunk_column_input, ia_model_input, detail_level_input, language_input, additional_specs_input,
412
+ max_output_tokens_input, export_format_input, use_personal_key_input, personal_api_key_input
413
+ ],
414
+ outputs=[
415
+ plot_output, table_output, analysis_output, code_output, report_output, status_output
416
+ ]
417
+ )
418
 
419
  if __name__ == "__main__":
420
+ if not os.path.exists("examples"):
421
+ os.makedirs("examples")
422
+ print("Carpeta 'examples' creada. Por favor, aรฑade 'video1.mp4', 'video2.mp4', y 'archivo.xlsx' dentro.")
423
+ demo.launch(show_error=True, debug=True)