Lukeetah commited on
Commit
eb15f64
·
verified ·
1 Parent(s): dbab579

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -256
app.py CHANGED
@@ -1,312 +1,165 @@
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 = """
9
  /* Importar fuente Inter */
10
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
-
12
- /* Variables globales */
13
  :root {
14
- --primary-color: #8b5cf6;
15
- --primary-hover: #7c3aed;
16
- --secondary-color: #f8fafc;
17
- --text-primary: #1e293b;
18
- --text-secondary: #64748b;
19
- --border-color: #e2e8f0;
20
- --success-color: #10b981;
21
- --error-color: #ef4444;
22
- --warning-color: #f59e0b;
23
  --gradient-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
24
  }
25
-
26
- /* Reset y configuración base */
27
- * {
28
- box-sizing: border-box;
29
- }
30
-
31
  body {
32
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
33
- background: var(--gradient-bg);
34
- margin: 0;
35
- padding: 0;
36
- min-height: 100vh;
37
  }
38
-
39
- /* Contenedor principal */
40
  .gradio-container {
41
- max-width: 800px !important;
42
- margin: 0 auto !important;
43
- padding: 2rem 1rem !important;
44
- background: rgba(255, 255, 255, 0.95);
45
- backdrop-filter: blur(10px);
46
- border-radius: 24px;
47
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
48
- margin-top: 2rem !important;
49
- margin-bottom: 2rem !important;
50
  }
51
-
52
- /* Título principal */
53
  .gradio-container h1 {
54
- color: var(--text-primary);
55
- font-size: 2.5rem;
56
- font-weight: 700;
57
- text-align: center;
58
- margin-bottom: 0.5rem;
59
  background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
60
- -webkit-background-clip: text;
61
- -webkit-text-fill-color: transparent;
62
- background-clip: text;
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;
70
- margin-bottom: 2rem;
71
- line-height: 1.6;
72
  }
73
-
74
- /* Campos de entrada */
75
  .gr-textbox {
76
- border: 2px solid var(--border-color) !important;
77
- border-radius: 12px !important;
78
- padding: 12px 16px !important;
79
- font-size: 1rem !important;
80
- transition: all 0.3s ease !important;
81
- background: white !important;
82
  }
83
-
84
  .gr-textbox:focus {
85
  border-color: var(--primary-color) !important;
86
- box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1) !important;
87
- outline: none !important;
88
  }
89
-
90
- /* Botones */
91
  .gr-button {
92
- background: var(--primary-color) !important;
93
- color: white !important;
94
- border: none !important;
95
- border-radius: 12px !important;
96
- padding: 12px 24px !important;
97
- font-size: 1rem !important;
98
- font-weight: 600 !important;
99
- cursor: pointer !important;
100
- transition: all 0.3s ease !important;
101
- text-transform: none !important;
102
- letter-spacing: 0.025em !important;
103
  }
104
-
105
  .gr-button:hover {
106
- background: var(--primary-hover) !important;
107
- transform: translateY(-2px) !important;
108
  box-shadow: 0 10px 25px -5px rgba(139, 92, 246, 0.4) !important;
109
  }
110
-
111
- .gr-button:active {
112
- transform: translateY(0) !important;
113
- }
114
-
115
- /* Radio buttons */
116
- .gr-radio {
117
- margin: 1rem 0 !important;
118
- }
119
-
120
- .gr-radio label {
121
- font-weight: 500 !important;
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 */
134
  .gr-file {
135
- border: 2px dashed var(--border-color) !important;
136
- border-radius: 12px !important;
137
- padding: 2rem !important;
138
- text-align: center !important;
139
- background: var(--secondary-color) !important;
140
- transition: all 0.3s ease !important;
141
  }
142
-
143
  .gr-file:hover {
144
- border-color: var(--primary-color) !important;
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;
152
- background: var(--border-color);
153
- border-radius: 3px;
154
- overflow: hidden;
155
- margin: 1rem 0;
156
- }
157
-
158
- .progress-fill {
159
- height: 100%;
160
- background: var(--primary-color);
161
- border-radius: 3px;
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;
169
- color: var(--success-color) !important;
170
- border-radius: 8px !important;
171
- padding: 12px 16px !important;
172
- margin: 1rem 0 !important;
173
- }
174
-
175
- .error-message {
176
- background: rgba(239, 68, 68, 0.1) !important;
177
- border: 1px solid var(--error-color) !important;
178
- color: var(--error-color) !important;
179
- border-radius: 8px !important;
180
- padding: 12px 16px !important;
181
- margin: 1rem 0 !important;
182
- }
183
-
184
- /* Responsive design */
185
  @media (max-width: 768px) {
186
- .gradio-container {
187
- margin: 1rem !important;
188
- padding: 1.5rem 1rem !important;
189
- border-radius: 16px !important;
190
- }
191
-
192
- .gradio-container h1 {
193
- font-size: 2rem !important;
194
- }
195
-
196
- .gradio-container p.subtitle {
197
- font-size: 1rem !important;
198
- }
199
  }
200
-
201
- /* Footer */
202
  .footer {
203
- text-align: center;
204
- margin-top: 2rem;
205
- padding-top: 2rem;
206
- border-top: 1px solid var(--border-color);
207
- color: var(--text-secondary);
208
- font-size: 0.875rem;
209
- }
210
-
211
- /* Animaciones sutiles */
212
- @keyframes fadeIn {
213
- from {
214
- opacity: 0;
215
- transform: translateY(20px);
216
- }
217
- to {
218
- opacity: 1;
219
- transform: translateY(0);
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
  """
228
 
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)
269
- else: # TXT
270
  result = scraper.scrape_to_text(normalized_url)
271
 
272
  progress(0.9, desc="Finalizando...")
273
 
274
  if result['status'] == 'success':
275
  progress(1.0, desc="¡Completado!")
276
- success_msg = f"""✅ **Procesamiento exitoso**
 
 
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
 
288
- 🔗 **URL:** {result.get('url', url)}
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;">
@@ -315,48 +168,42 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scrape
315
  </div>
316
  """)
317
 
318
- with gr.Row():
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("""
@@ -364,10 +211,11 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scrape
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.
@@ -375,26 +223,23 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scrape
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
  )
 
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import os
4
+ from web_scraper_tool import WebScrapperTool
 
5
 
6
+ # CSS personalizado (sin cambios)
7
  custom_css = """
8
  /* Importar fuente Inter */
9
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
 
 
10
  :root {
11
+ --primary-color: #8b5cf6; --primary-hover: #7c3aed; --secondary-color: #f8fafc;
12
+ --text-primary: #1e293b; --text-secondary: #64748b; --border-color: #e2e8f0;
13
+ --success-color: #10b981; --error-color: #ef4444; --warning-color: #f59e0b;
 
 
 
 
 
 
14
  --gradient-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
15
  }
16
+ * { box-sizing: border-box; }
 
 
 
 
 
17
  body {
18
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
19
+ background: var(--gradient-bg); margin: 0; padding: 0; min-height: 100vh;
 
 
 
20
  }
 
 
21
  .gradio-container {
22
+ max-width: 800px !important; margin: 0 auto !important; padding: 2rem 1rem !important;
23
+ background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px);
24
+ border-radius: 24px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
25
+ margin-top: 2rem !important; margin-bottom: 2rem !important;
 
 
 
 
 
26
  }
 
 
27
  .gradio-container h1 {
28
+ color: var(--text-primary); font-size: 2.5rem; font-weight: 700; text-align: center; margin-bottom: 0.5rem;
 
 
 
 
29
  background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
30
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
 
 
31
  }
32
+ .gradio-container p.subtitle {
33
+ color: var(--text-secondary); font-size: 1.125rem; text-align: center; margin-bottom: 2rem; line-height: 1.6;
 
 
 
 
 
 
34
  }
 
 
35
  .gr-textbox {
36
+ border: 2px solid var(--border-color) !important; border-radius: 12px !important;
37
+ padding: 12px 16px !important; font-size: 1rem !important;
38
+ transition: all 0.3s ease !important; background: white !important;
 
 
 
39
  }
 
40
  .gr-textbox:focus {
41
  border-color: var(--primary-color) !important;
42
+ box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1) !important; outline: none !important;
 
43
  }
 
 
44
  .gr-button {
45
+ background: var(--primary-color) !important; color: white !important; border: none !important;
46
+ border-radius: 12px !important; padding: 12px 24px !important; font-size: 1rem !important;
47
+ font-weight: 600 !important; cursor: pointer !important; transition: all 0.3s ease !important;
48
+ text-transform: none !important; letter-spacing: 0.025em !important;
 
 
 
 
 
 
 
49
  }
 
50
  .gr-button:hover {
51
+ background: var(--primary-hover) !important; transform: translateY(-2px) !important;
 
52
  box-shadow: 0 10px 25px -5px rgba(139, 92, 246, 0.4) !important;
53
  }
54
+ .gr-button:active { transform: translateY(0) !important; }
55
+ .gr-radio { margin: 1rem 0 !important; }
56
+ .gr-radio label { font-weight: 500 !important; color: var(--text-primary) !important; }
57
+ .validation-output-container .gr-textbox textarea {
58
+ font-family: 'Inter', monospace !important; font-size: 0.9rem !important;
59
+ padding: 8px 12px !important; min-height: 40px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
 
 
61
  .gr-file {
62
+ border: 2px dashed var(--border-color) !important; border-radius: 12px !important;
63
+ padding: 2rem !important; text-align: center !important;
64
+ background: var(--secondary-color) !important; transition: all 0.3s ease !important;
 
 
 
65
  }
 
66
  .gr-file:hover {
67
+ border-color: var(--primary-color) !important; background: rgba(139, 92, 246, 0.05) !important;
 
68
  }
69
+ .progress-bar { width: 100%; height: 6px; background: var(--border-color); border-radius: 3px; overflow: hidden; margin: 1rem 0; }
70
+ .progress-fill { height: 100%; background: var(--primary-color); border-radius: 3px; transition: width 0.3s ease; }
71
+ .success-message { /* ... */ } .error-message { /* ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  @media (max-width: 768px) {
73
+ .gradio-container { margin: 1rem !important; padding: 1.5rem 1rem !important; border-radius: 16px !important; }
74
+ .gradio-container h1 { font-size: 2rem !important; }
75
+ .gradio-container p.subtitle { font-size: 1rem !important; }
 
 
 
 
 
 
 
 
 
 
76
  }
 
 
77
  .footer {
78
+ text-align: center; margin-top: 2rem; padding-top: 2rem;
79
+ border-top: 1px solid var(--border-color); color: var(--text-secondary); font-size: 0.875rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
82
+ .gradio-container > div > .gr-form > * { animation: fadeIn 0.6s ease forwards; }
83
  """
84
 
 
85
  scraper = WebScrapperTool()
86
 
87
  def validate_url_live(url: str):
 
88
  if not url or not url.strip():
89
+ return gr.Textbox(value="", placeholder="Estado de la URL", label="Estado de la URL")
 
 
90
  try:
91
+ normalized_url = scraper.normalize_url(url.strip())
92
+ return gr.Textbox(value=f"✅ URL normalizada: {normalized_url}", interactive=False, label="Estado de la URL")
 
 
93
  except Exception as e:
94
+ return gr.Textbox(value=f"⚠️ Formato de URL inválido o necesita ajuste: {str(e)}", interactive=False, label="Estado de la URL")
95
 
96
  def process_url_and_update_ui(url: str, format_choice: str, progress=gr.Progress(track_tqdm=True)):
 
97
  if not url or not url.strip():
98
  return "❌ Por favor ingresa una URL.", None, "Ingresa una URL válida primero."
99
 
100
+ normalized_url_display_val = ""
101
  try:
102
  progress(0.1, desc="Normalizando URL...")
103
  normalized_url = scraper.normalize_url(url.strip())
104
+ normalized_url_display_val = f"URL procesada: {normalized_url}"
105
 
106
  progress(0.2, desc="Detectando tipo de contenido...")
107
  is_image = scraper.is_image_url(normalized_url)
108
+ # Una llamada _get_content preliminar podría ser muy costosa solo para tipo.
109
+ # Mejor basarse en la extensión y luego en headers durante el procesamiento real.
110
+ content_type_detected = "🖼️ Imagen (probable)" if is_image else "📄 Página web (probable)"
111
 
112
+ progress(0.4, desc=f"Extrayendo contenido como {format_choice}...")
 
 
 
 
113
 
114
  if format_choice == "PDF":
115
  result = scraper.scrape_to_pdf(normalized_url)
116
+ else:
117
  result = scraper.scrape_to_text(normalized_url)
118
 
119
  progress(0.9, desc="Finalizando...")
120
 
121
  if result['status'] == 'success':
122
  progress(1.0, desc="¡Completado!")
123
+ # Tratar de obtener tipo de contenido del resultado si la herramienta lo provee explícitamente.
124
+ # Por ahora, nos basamos en la detección inicial.
125
+ success_msg = f"""## ✅ **Procesamiento exitoso**
126
 
127
+ **🔗 URL original:** `{url}`
128
+ **⚙️ URL procesada:** `{result['url']}`
129
+ **📁 Archivo generado:** `{os.path.basename(result['file'])}`
130
+ **📊 Tipo detectado (inicial):** `{content_type_detected}`
131
+ **📄 Formato de salida:** `{format_choice}`
132
 
133
+ 💡 **Listo para Copilot:** El archivo está optimizado para ser procesado.
134
+ """
135
+ return success_msg, result['file'], f"✅ {normalized_url_display_val}"
136
  else:
137
+ error_msg = f"""## ❌ **Error en el procesamiento**
138
 
139
+ **🔗 URL intentada:** `{result.get('url', url)}`
140
+ **⚠️ Error:** `{result['message']}`
141
 
142
  💡 **Sugerencias:**
143
  - Verifica que la URL sea accesible y correcta.
144
  - Intenta con una URL diferente.
145
  - Algunos sitios pueden bloquear el scraping.
146
+ - Si es PDF y el error es por caracteres, intenta TXT.
147
+ - Asegúrate que la URL apunte a contenido textual para TXT, o imagen/texto para PDF.
148
+ """
149
+ return error_msg, None, f"⚠️ Error con {url}. {normalized_url_display_val}"
150
 
151
  except Exception as e:
152
  import traceback
153
  tb_str = traceback.format_exc()
154
+ error_msg = f"""## ❌ **Error inesperado en la aplicación**
 
 
 
155
 
156
+ **⚠️ Error:** `{str(e)}`
157
+ **📄 Detalles:**
158
+ {tb_str[:400]}...
159
+ 💡 **Intenta nuevamente o revisa la URL. Si el problema persiste, revisa los logs del servidor.**
160
+ """
161
+ return error_msg, None, f"💥 Error crítico procesando {url}. {normalized_url_display_val if normalized_url_display_val else 'URL no pudo ser normalizada.'}"
162
 
 
163
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="🕸️ Web Scraper Tool") as demo:
164
  gr.HTML("""
165
  <div style="text-align: center; margin-bottom: 2rem;">
 
168
  </div>
169
  """)
170
 
171
+ with gr.Row(equal_height=False):
172
  with gr.Column(scale=3):
173
  url_input = gr.Textbox(
174
  label="🔗 URL de la página web",
175
  placeholder="Ej: https://www.ejemplo.com/articulo o www.clarin.com.ar",
176
+ info="Ingresa la URL completa. Se intentará normalizar (ej. añadir https://).",
177
  lines=1,
178
+ elem_id="url-input"
179
  )
180
+ with gr.Column(scale=1, min_width=200):
 
181
  format_choice = gr.Radio(
182
  choices=["PDF", "TXT"],
183
  value="TXT",
184
  label="📄 Formato de salida",
185
+ info="PDF para visual/imágenes, TXT para texto plano."
186
  )
187
 
 
 
188
  validation_output_display = gr.Textbox(
189
  label="Estado de la URL",
190
  interactive=False,
 
191
  placeholder="La URL normalizada o mensajes de validación aparecerán aquí...",
192
  lines=1,
193
  elem_classes=["validation-output-container"]
194
  )
195
 
196
+ process_btn = gr.Button("🚀 Extraer y Convertir", variant="primary", size="lg", elem_id="process-button")
 
 
 
197
 
198
  with gr.Accordion("📊 Resultado del Procesamiento y Archivo", open=True):
199
+ result_output = gr.Markdown(
200
+ value="Esperando procesamiento...",
201
+ elem_id="result-markdown"
202
  )
203
  file_output = gr.File(
204
  label="📁 Archivo generado (haz clic para descargar)",
205
+ interactive=False,
206
+ elem_id="file-output"
207
  )
208
 
209
  gr.HTML("""
 
211
  <h3>ℹ️ Información de uso</h3>
212
  <ul style="text-align: left; max-width: 600px; margin: 0 auto; padding-left: 20px;">
213
  <li><strong>URLs flexibles:</strong> Intenta normalizar URLs incompletas (e.g., añadiendo https://).</li>
214
+ <li><strong>Detección de contenido:</strong> Intenta identificar si la URL es una imagen o una página web.</li>
215
  <li><strong>Optimizado para Copilot:</strong> Los archivos generados buscan compatibilidad.</li>
216
+ <li><strong>Formatos:</strong> PDF (preserva estructura visual si es texto, o imagen directa) y TXT (texto plano).</li>
217
+ <li><strong>Codificación:</strong> UTF-8 para TXT. Soporte Unicode para PDF con fuentes DejaVu (si están disponibles en el servidor) o Arial.</li>
218
+ <li><strong>Fuente PDF:</strong> Si `DejaVuSansCondensed.ttf` está en el directorio del servidor o en una carpeta `fonts`, se usará para mejor soporte Unicode en PDFs.</li>
219
  </ul>
220
  <p style="margin-top: 1rem; color: #64748b;">
221
  Desarrollado con ❤️ para facilitar la integración con herramientas de IA.
 
223
  </div>
224
  """)
225
 
 
226
  url_input.change(
227
  fn=validate_url_live,
228
  inputs=[url_input],
229
  outputs=[validation_output_display],
 
 
230
  )
231
 
232
  process_btn.click(
233
  fn=process_url_and_update_ui,
234
  inputs=[url_input, format_choice],
235
+ outputs=[result_output, file_output, validation_output_display]
236
  )
237
 
238
  if __name__ == "__main__":
239
  demo.launch(
240
+ server_name="0.0.0.0",
241
+ server_port=int(os.environ.get('PORT', 7860)), # Para compatibilidad con plataformas que definen PORT
242
+ show_error=True,
243
+ debug=True, # Muestra más información en consola
244
+ # share=True # Descomentar para enlace público temporal si se ejecuta localmente y se quiere compartir
245
  )