Lukeetah commited on
Commit
91cc315
·
verified ·
1 Parent(s): 1a02fc8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -95
app.py CHANGED
@@ -1,8 +1,8 @@
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import os
4
- # import tempfile # No se usa directamente aquí, pero podría ser usado por WebScrapperTool
5
- from web_scraper_tool import WebScrapperTool # Asegúrate que este archivo también esté en UTF-8 si tiene strings
6
 
7
  # CSS personalizado con estética minimalista profesional
8
  custom_css = """
@@ -63,7 +63,7 @@ body {
63
  }
64
 
65
  /* Subtítulo */
66
- .gradio-container p {
67
  color: var(--text-secondary);
68
  font-size: 1.125rem;
69
  text-align: center;
@@ -122,9 +122,12 @@ body {
122
  color: var(--text-primary) !important;
123
  }
124
 
125
- /* Mensajes de estado */
126
- .gr-textbox[data-testid="textbox"] {
127
  font-family: 'Inter', monospace !important;
 
 
 
128
  }
129
 
130
  /* Área de descarga */
@@ -142,7 +145,7 @@ body {
142
  background: rgba(139, 92, 246, 0.05) !important;
143
  }
144
 
145
- /* Indicadores de progreso */
146
  .progress-bar {
147
  width: 100%;
148
  height: 6px;
@@ -159,7 +162,7 @@ body {
159
  transition: width 0.3s ease;
160
  }
161
 
162
- /* Estados de mensajes */
163
  .success-message {
164
  background: rgba(16, 185, 129, 0.1) !important;
165
  border: 1px solid var(--success-color) !important;
@@ -190,7 +193,7 @@ body {
190
  font-size: 2rem !important;
191
  }
192
 
193
- .gradio-container p {
194
  font-size: 1rem !important;
195
  }
196
  }
@@ -217,7 +220,8 @@ body {
217
  }
218
  }
219
 
220
- .gradio-container > * {
 
221
  animation: fadeIn 0.6s ease forwards;
222
  }
223
  """
@@ -225,40 +229,40 @@ body {
225
  # Inicializar la herramienta de scraping
226
  scraper = WebScrapperTool()
227
 
228
- def validate_url(url):
229
- """Valida la URL ingresada"""
230
  if not url or not url.strip():
231
- return False, "❌ Por favor ingresa una URL válida"
 
232
 
233
  try:
234
- normalized = scraper.normalize_url(url.strip())
235
- return True, f"✅ URL válida: {normalized}"
 
 
236
  except Exception as e:
237
- return False, f" Error en URL: {str(e)}"
238
 
239
- def process_url(url, format_choice, progress=gr.Progress()):
240
- """Procesa la URL y genera el archivo en el formato seleccionado"""
241
  if not url or not url.strip():
242
- return "❌ Por favor ingresa una URL válida", None
243
 
 
244
  try:
245
- # Validar URL
246
- progress(0.1, desc="Validando URL...")
247
- is_valid, message = validate_url(url)
248
- if not is_valid:
249
- return message, None
250
-
251
- # Normalizar URL
252
- progress(0.2, desc="Normalizando URL...")
253
  normalized_url = scraper.normalize_url(url.strip())
 
254
 
255
- # Detectar tipo de contenido
256
- progress(0.3, desc="Detectando tipo de contenido...")
257
  is_image = scraper.is_image_url(normalized_url)
258
- content_type = "🖼️ Imagen" if is_image else "📄 Página web"
 
 
 
 
259
 
260
- # Procesar según formato seleccionado
261
- progress(0.5, desc=f"Extrayendo contenido ({format_choice})...")
262
 
263
  if format_choice == "PDF":
264
  result = scraper.scrape_to_pdf(normalized_url)
@@ -273,12 +277,11 @@ def process_url(url, format_choice, progress=gr.Progress()):
273
 
274
  🔗 **URL procesada:** {result['url']}
275
  📁 **Archivo generado:** {os.path.basename(result['file'])}
276
- 📊 **Tipo de contenido:** {content_type}
277
  📄 **Formato de salida:** {format_choice}
278
 
279
- 💡 **Listo para Copilot:** El archivo está optimizado para ser procesado por Microsoft Copilot"""
280
-
281
- return success_msg, result['file']
282
  else:
283
  error_msg = f"""❌ **Error en el procesamiento**
284
 
@@ -286,28 +289,29 @@ def process_url(url, format_choice, progress=gr.Progress()):
286
  ⚠️ **Error:** {result['message']}
287
 
288
  💡 **Sugerencias:**
289
- - Verifica que la URL esté accesible
290
- - Intenta con una URL diferente
291
- - Algunos sitios pueden bloquear el scraping automático"""
292
-
293
- return error_msg, None
294
 
295
  except Exception as e:
296
- error_msg = f"""❌ **Error inesperado**
 
 
297
 
298
  ⚠️ **Error:** {str(e)}
 
299
 
300
- 💡 **Intenta nuevamente con una URL diferente**"""
301
-
302
- return error_msg, None
303
 
304
  # Crear interfaz Gradio
305
- # LA LÍNEA CRÍTICA ESTÁ AQUÍ: theme=gr.themes.Soft()
306
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scraper Tool") as demo:
307
  gr.HTML("""
308
  <div style="text-align: center; margin-bottom: 2rem;">
309
  <h1>🕸️ Web Scraper Tool</h1>
310
- <p>Extrae contenido de páginas web y conviértelo a formatos compatibles con Microsoft Copilot</p>
311
  </div>
312
  """)
313
 
@@ -315,86 +319,82 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scrape
315
  with gr.Column(scale=3):
316
  url_input = gr.Textbox(
317
  label="🔗 URL de la página web",
318
- placeholder="https://ejemplo.com o Https://EJEMPLO.com (mayúsculas OK)",
319
- info="Soporta URLs con cualquier formato de mayúsculas/minúsculas",
320
- lines=1
 
321
  )
322
 
323
- with gr.Column(scale=1):
324
  format_choice = gr.Radio(
325
  choices=["PDF", "TXT"],
326
  value="TXT",
327
  label="📄 Formato de salida",
328
- info="Ambos formatos son compatibles con Copilot"
329
  )
330
-
331
- # Botón de validación en tiempo real
332
- validate_btn = gr.Button("🔍 Validar URL", variant="secondary", size="sm")
333
- validation_output = gr.Textbox(
334
- label="Estado de validación",
335
  interactive=False,
336
- show_label=False
 
 
 
337
  )
 
 
 
338
 
339
- # Botón principal de procesamiento
340
  process_btn = gr.Button("🚀 Extraer y Convertir", variant="primary", size="lg")
341
 
342
- # Área de resultados
343
- with gr.Row():
344
- with gr.Column(scale=2):
345
- result_output = gr.Textbox(
346
- label="📊 Resultado del procesamiento",
347
- lines=8,
348
- interactive=False
349
- )
 
350
 
351
- with gr.Column(scale=1):
352
- file_output = gr.File(
353
- label="📁 Archivo generado",
354
- interactive=False
355
- )
356
-
357
- # Información adicional
358
  gr.HTML("""
359
  <div class="footer">
360
  <h3>ℹ️ Información de uso</h3>
361
- <ul style="text-align: left; max-width: 600px; margin: 0 auto;">
362
- <li><strong>URLs flexibles:</strong> Funciona con cualquier formato (HTTP, HTTPS, con/sin www)</li>
363
- <li><strong>Detección automática:</strong> Identifica si el contenido es una imagen o texto</li>
364
- <li><strong>Optimizado para Copilot:</strong> Los archivos generados están formateados para Microsoft Copilot</li>
365
- <li><strong>Formatos soportados:</strong> PDF (con formato visual) y TXT (texto plano)</li>
 
366
  </ul>
367
  <p style="margin-top: 1rem; color: #64748b;">
368
- Desarrollado con ❤️ para maximizar la compatibilidad con herramientas de IA
369
  </p>
370
  </div>
371
  """)
372
 
373
  # Configurar eventos
374
- validate_btn.click(
375
- fn=lambda url: validate_url(url)[1],
376
  inputs=[url_input],
377
- outputs=[validation_output]
 
 
378
  )
379
 
380
  process_btn.click(
381
- fn=process_url,
382
  inputs=[url_input, format_choice],
383
- outputs=[result_output, file_output]
384
- )
385
-
386
- # Validación automática al cambiar la URL
387
- url_input.change(
388
- fn=lambda url: validate_url(url)[1] if url else "",
389
- inputs=[url_input],
390
- outputs=[validation_output]
391
  )
392
 
393
- # Configuración para Hugging Face Spaces
394
  if __name__ == "__main__":
395
  demo.launch(
396
- server_name="0.0.0.0",
397
  server_port=7860,
398
- show_error=True,
399
- share=False # Cambia a True si quieres un enlace público temporal para probar
 
400
  )
 
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import os
4
+ # import tempfile # No se usa directamente aquí, pero es usado por WebScrapperTool
5
+ from web_scraper_tool import WebScrapperTool # Asegúrate que este archivo también esté en UTF-8
6
 
7
  # CSS personalizado con estética minimalista profesional
8
  custom_css = """
 
63
  }
64
 
65
  /* Subtítulo */
66
+ .gradio-container p.subtitle { /* Añadido .subtitle para especificidad */
67
  color: var(--text-secondary);
68
  font-size: 1.125rem;
69
  text-align: center;
 
122
  color: var(--text-primary) !important;
123
  }
124
 
125
+ /* Mensajes de estado (validation_output) */
126
+ .validation-output-container .gr-textbox textarea { /* Específico para el output de validación */
127
  font-family: 'Inter', monospace !important;
128
+ font-size: 0.9rem !important;
129
+ padding: 8px 12px !important; /* Más pequeño */
130
+ min-height: 40px !important; /* Altura ajustada */
131
  }
132
 
133
  /* Área de descarga */
 
145
  background: rgba(139, 92, 246, 0.05) !important;
146
  }
147
 
148
+ /* Indicadores de progreso (si se usan explícitamente) */
149
  .progress-bar {
150
  width: 100%;
151
  height: 6px;
 
162
  transition: width 0.3s ease;
163
  }
164
 
165
+ /* Estados de mensajes (no se usan clases directas, pero se mantienen por si acaso) */
166
  .success-message {
167
  background: rgba(16, 185, 129, 0.1) !important;
168
  border: 1px solid var(--success-color) !important;
 
193
  font-size: 2rem !important;
194
  }
195
 
196
+ .gradio-container p.subtitle {
197
  font-size: 1rem !important;
198
  }
199
  }
 
220
  }
221
  }
222
 
223
+ /* Aplicar animación a elementos principales dentro del contenedor */
224
+ .gradio-container > div > .gr-form > * { /* Más específico para los bloques de Gradio */
225
  animation: fadeIn 0.6s ease forwards;
226
  }
227
  """
 
229
  # Inicializar la herramienta de scraping
230
  scraper = WebScrapperTool()
231
 
232
+ def validate_url_live(url: str):
233
+ """Valida la URL ingresada para actualización en vivo."""
234
  if not url or not url.strip():
235
+ # No mostrar error si está vacío, solo limpiar
236
+ return gr.Textbox(value="", placeholder="Estado de validación")
237
 
238
  try:
239
+ normalized_url = scraper.normalize_url(url)
240
+ # Aquí podrías añadir una comprobación rápida de conexión si quieres,
241
+ # pero normalize_url no hace llamadas de red.
242
+ return gr.Textbox(value=f"✅ URL normalizada (lista para procesar): {normalized_url}", interactive=False)
243
  except Exception as e:
244
+ return gr.Textbox(value=f"⚠️ Posible problema con URL: {str(e)}", interactive=False)
245
 
246
+ def process_url_and_update_ui(url: str, format_choice: str, progress=gr.Progress(track_tqdm=True)):
247
+ """Procesa la URL y genera el archivo en el formato seleccionado, actualizando la UI."""
248
  if not url or not url.strip():
249
+ return "❌ Por favor ingresa una URL.", None, "Ingresa una URL válida primero."
250
 
251
+ validation_message = ""
252
  try:
253
+ progress(0.1, desc="Normalizando URL...")
 
 
 
 
 
 
 
254
  normalized_url = scraper.normalize_url(url.strip())
255
+ validation_message = f"✅ URL normalizada: {normalized_url}"
256
 
257
+ progress(0.2, desc="Detectando tipo de contenido...")
 
258
  is_image = scraper.is_image_url(normalized_url)
259
+ content_type_detected = "🖼️ Imagen" if is_image else "📄 Página web"
260
+
261
+ # Actualizar el textbox de validación con la URL normalizada
262
+ # Esto no se puede hacer directamente desde aquí si validate_url_live es el único que lo actualiza.
263
+ # En su lugar, el mensaje de resultado lo incluirá.
264
 
265
+ progress(0.4, desc=f"Extrayendo contenido ({format_choice})...")
 
266
 
267
  if format_choice == "PDF":
268
  result = scraper.scrape_to_pdf(normalized_url)
 
277
 
278
  🔗 **URL procesada:** {result['url']}
279
  📁 **Archivo generado:** {os.path.basename(result['file'])}
280
+ 📊 **Tipo de contenido:** {content_type_detected}
281
  📄 **Formato de salida:** {format_choice}
282
 
283
+ 💡 **Listo para Copilot:** El archivo está optimizado para ser procesado por Microsoft Copilot."""
284
+ return success_msg, result['file'], validation_message # Devolver mensaje de validación también
 
285
  else:
286
  error_msg = f"""❌ **Error en el procesamiento**
287
 
 
289
  ⚠️ **Error:** {result['message']}
290
 
291
  💡 **Sugerencias:**
292
+ - Verifica que la URL sea accesible y correcta.
293
+ - Intenta con una URL diferente.
294
+ - Algunos sitios pueden bloquear el scraping.
295
+ - Si es PDF, el contenido podría tener caracteres no soportados (intenta TXT)."""
296
+ return error_msg, None, validation_message
297
 
298
  except Exception as e:
299
+ import traceback
300
+ tb_str = traceback.format_exc()
301
+ error_msg = f"""❌ **Error inesperado en la aplicación**
302
 
303
  ⚠️ **Error:** {str(e)}
304
+ 📄 **Detalles:** {tb_str[:300]}...
305
 
306
+ 💡 **Intenta nuevamente o revisa la URL.**"""
307
+ return error_msg, None, validation_message if validation_message else f"Error antes de normalizar: {str(e)}"
 
308
 
309
  # Crear interfaz Gradio
 
310
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scraper Tool") as demo:
311
  gr.HTML("""
312
  <div style="text-align: center; margin-bottom: 2rem;">
313
  <h1>🕸️ Web Scraper Tool</h1>
314
+ <p class="subtitle">Extrae contenido de páginas web y conviértelo a formatos compatibles con Microsoft Copilot</p>
315
  </div>
316
  """)
317
 
 
319
  with gr.Column(scale=3):
320
  url_input = gr.Textbox(
321
  label="🔗 URL de la página web",
322
+ placeholder="Ej: https://www.ejemplo.com/articulo o www.clarin.com.ar",
323
+ info="Ingresa la URL completa incluyendo http:// o https:// si es posible.",
324
+ lines=1,
325
+ elem_id="url-input" # Para CSS si es necesario
326
  )
327
 
328
+ with gr.Column(scale=1, min_width=180): # Asegurar ancho mínimo para el radio
329
  format_choice = gr.Radio(
330
  choices=["PDF", "TXT"],
331
  value="TXT",
332
  label="📄 Formato de salida",
333
+ info="Ambos son compatibles con Copilot."
334
  )
335
+
336
+ # Textbox para mostrar el estado de validación/normalización en vivo
337
+ # Usamos elem_classes para aplicar CSS específico si es necesario
338
+ validation_output_display = gr.Textbox(
339
+ label="Estado de la URL",
340
  interactive=False,
341
+ show_label=True, # Mostrar la etiqueta
342
+ placeholder="La URL normalizada o mensajes de validación aparecerán aquí...",
343
+ lines=1,
344
+ elem_classes=["validation-output-container"]
345
  )
346
+
347
+ # No se necesita botón de validación separado si se hace en vivo con url_input.change
348
+ # validate_btn = gr.Button("🔍 Validar URL", variant="secondary", size="sm")
349
 
 
350
  process_btn = gr.Button("🚀 Extraer y Convertir", variant="primary", size="lg")
351
 
352
+ with gr.Accordion("📊 Resultado del Procesamiento y Archivo", open=True):
353
+ result_output = gr.Markdown( # Usar Markdown para mejor formato de mensajes
354
+ label="Mensaje de Resultado",
355
+ value="Esperando procesamiento..."
356
+ )
357
+ file_output = gr.File(
358
+ label="📁 Archivo generado (haz clic para descargar)",
359
+ interactive=False # El usuario no sube, solo descarga
360
+ )
361
 
 
 
 
 
 
 
 
362
  gr.HTML("""
363
  <div class="footer">
364
  <h3>ℹ️ Información de uso</h3>
365
+ <ul style="text-align: left; max-width: 600px; margin: 0 auto; padding-left: 20px;">
366
+ <li><strong>URLs flexibles:</strong> Intenta normalizar URLs incompletas (e.g., añadiendo https://).</li>
367
+ <li><strong>Detección de contenido:</strong> Identifica si la URL es una imagen o una página web.</li>
368
+ <li><strong>Optimizado para Copilot:</strong> Los archivos generados buscan compatibilidad.</li>
369
+ <li><strong>Formatos:</strong> PDF (preserva algo de estructura visual si es texto, o imagen directa) y TXT (texto plano).</li>
370
+ <li><strong>Codificación:</strong> Se usa UTF-8 para TXT y se intenta el mejor soporte Unicode para PDF con fuentes DejaVu (si están disponibles) o Arial.</li>
371
  </ul>
372
  <p style="margin-top: 1rem; color: #64748b;">
373
+ Desarrollado con ❤️ para facilitar la integración con herramientas de IA.
374
  </p>
375
  </div>
376
  """)
377
 
378
  # Configurar eventos
379
+ url_input.change(
380
+ fn=validate_url_live,
381
  inputs=[url_input],
382
+ outputs=[validation_output_display],
383
+ # Trigger más rápido para sensación de "en vivo"
384
+ # every=0.5 # Descomentar si se quiere validación periódica mientras se escribe, puede ser mucho
385
  )
386
 
387
  process_btn.click(
388
+ fn=process_url_and_update_ui,
389
  inputs=[url_input, format_choice],
390
+ outputs=[result_output, file_output, validation_output_display] # También actualiza el display de validación
 
 
 
 
 
 
 
391
  )
392
 
 
393
  if __name__ == "__main__":
394
  demo.launch(
395
+ server_name="0.0.0.0", # Escucha en todas las interfaces de red
396
  server_port=7860,
397
+ show_error=True, # Muestra errores detallados en la consola y navegador
398
+ debug=True, # Habilita modo debug de Gradio
399
+ # share=True # Cambia a True si necesitas un enlace público temporal (expira en 72h)
400
  )