angelsg213 commited on
Commit
95b72cc
·
verified ·
1 Parent(s): 6e58e9b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -399
app.py CHANGED
@@ -12,37 +12,7 @@ from reportlab.lib.units import inch
12
  from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
13
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
14
  from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
15
- from pdf2image import convert_from_path
16
- import base64
17
- from io import BytesIO
18
- from PIL import Image as PILImage
19
-
20
- # ============= CONVERTIR PDF A IMÁGENES =============
21
- def pdf_to_images(pdf_path):
22
- """Convierte cada página del PDF en una imagen"""
23
- try:
24
- from pdf2image import convert_from_path
25
- images = convert_from_path(pdf_path, dpi=200)
26
- return images
27
- except ImportError:
28
- print("⚠️ pdf2image no está instalado o poppler no está disponible")
29
- return []
30
- except Exception as e:
31
- print(f"Error convirtiendo PDF a imágenes: {str(e)}")
32
- # Intentar con método alternativo usando PIL
33
- try:
34
- from PIL import Image
35
- import fitz # PyMuPDF como alternativa
36
- doc = fitz.open(pdf_path)
37
- images = []
38
- for page in doc:
39
- pix = page.get_pixmap(dpi=200)
40
- img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
41
- images.append(img)
42
- doc.close()
43
- return images
44
- except:
45
- return []
46
 
47
  # ============= EXTRAER TEXTO DEL PDF =============
48
  def extraer_texto_pdf(pdf_file):
@@ -55,261 +25,106 @@ def extraer_texto_pdf(pdf_file):
55
  except Exception as e:
56
  return f"Error: {str(e)}"
57
 
58
- # ============= VQA - VISUAL QUESTION ANSWERING =============
59
- def analizar_con_vqa(pdf_path, pregunta_usuario="¿Qué información contiene esta factura?"):
60
- """Usa modelos de Visual Question Answering de Hugging Face"""
61
 
62
  token = os.getenv("aa")
63
  if not token:
64
- return "❌ Error: Falta configurar HF_TOKEN en Settings → Secrets"
65
-
66
- # Convertir primera página a imagen
67
- images = pdf_to_images(pdf_path)
68
- if not images:
69
- return "❌ No se pudo convertir el PDF a imagen. Instala poppler-utils o PyMuPDF (fitz) para habilitar esta funcionalidad.\n\n💡 Mientras tanto, usa las otras pestañas como 'Document QA' que funcionan con el texto extraído."
70
 
71
- primera_pagina = images[0]
72
-
73
- # Modelos VQA de Hugging Face (verificados y funcionales)
74
- modelos_vqa = [
75
- "dandelin/vilt-b32-finetuned-vqa",
76
- "Salesforce/blip-vqa-base",
77
- "Salesforce/blip2-opt-2.7b"
78
- ]
79
-
80
- client = InferenceClient(token=token)
81
- resultados = []
82
-
83
- for modelo in modelos_vqa:
84
- try:
85
- print(f"\n🔍 Probando VQA con: {modelo}")
86
-
87
- # Usar API de Hugging Face para VQA
88
- result = client.visual_question_answering(
89
- image=primera_pagina,
90
- question=pregunta_usuario,
91
- model=modelo
92
- )
93
-
94
- respuesta = result[0]['answer'] if isinstance(result, list) else str(result)
95
-
96
- resultados.append(f"**🤖 {modelo}**\n📝 Respuesta: {respuesta}\n")
97
- print(f"✅ Éxito con {modelo}")
98
-
99
- except Exception as e:
100
- print(f"❌ Error con {modelo}: {str(e)}")
101
- resultados.append(f"**{modelo}**: Error - {str(e)[:100]}\n")
102
 
103
- if resultados:
104
- return "\n".join(resultados)
105
- return "❌ No se pudo procesar con modelos VQA"
106
 
107
- # ============= DOCUMENT QA - QUESTION ANSWERING SOBRE TEXTO =============
108
- def analizar_con_document_qa(texto, pregunta_usuario="¿Cuál es el total de la factura?"):
109
- """Usa modelos de Question Answering de Hugging Face sobre documentos"""
110
-
111
- token = os.getenv("aa")
112
- if not token:
113
- return "❌ Error: Falta configurar HF_TOKEN"
114
-
115
- texto_limpio = texto[:3000] # Limitar contexto para los modelos
116
-
117
- # Modelos de Question Answering de Hugging Face
118
- modelos_qa = [
119
- "deepset/roberta-base-squad2",
120
- "distilbert-base-cased-distilled-squad",
121
- "deepset/bert-base-cased-squad2"
122
- ]
123
-
124
- client = InferenceClient(token=token)
125
- resultados = []
126
-
127
- for modelo in modelos_qa:
128
- try:
129
- print(f"\n📄 Probando Document QA con: {modelo}")
130
-
131
- response = client.question_answering(
132
- question=pregunta_usuario,
133
- context=texto_limpio,
134
- model=modelo
135
- )
136
-
137
- respuesta = response['answer']
138
- confianza = response['score']
139
-
140
- resultados.append(
141
- f"**🤖 {modelo}**\n"
142
- f"📝 Respuesta: **{respuesta}**\n"
143
- f"📊 Confianza: {confianza:.2%}\n"
144
- )
145
- print(f"✅ Éxito con {modelo}")
146
-
147
- except Exception as e:
148
- print(f"❌ Error con {modelo}: {str(e)}")
149
- resultados.append(f"**{modelo}**: Error\n")
150
-
151
- if resultados:
152
- return "\n".join(resultados)
153
- return "❌ No se pudo procesar con modelos Document QA"
154
 
155
- # ============= LAYOUT DOCUMENT QA =============
156
- def analizar_con_layout_qa(pdf_path, texto, pregunta_usuario="¿Cuál es el número de factura?"):
157
- """Usa modelos LayoutLM para entender documentos con layout visual"""
158
-
159
- token = os.getenv("aa")
160
- if not token:
161
- return "❌ Error: Falta configurar HF_TOKEN"
162
-
163
- # Modelos especializados en Document Understanding con layout
164
- modelos_layout = [
165
- "impira/layoutlm-document-qa",
166
- "microsoft/layoutlmv2-base-uncased",
167
- "nielsr/layoutlmv3-finetuned-funsd"
168
- ]
169
-
170
- client = InferenceClient(token=token)
171
- texto_limpio = texto[:2500]
172
- resultados = []
173
-
174
- for modelo in modelos_layout:
175
- try:
176
- print(f"\n📐 Probando Layout Document QA con: {modelo}")
177
-
178
- # Usar question answering sobre el texto extraído
179
- response = client.question_answering(
180
- question=pregunta_usuario,
181
- context=texto_limpio,
182
- model=modelo
183
- )
184
-
185
- respuesta = response['answer']
186
- confianza = response['score']
187
-
188
- resultados.append(
189
- f"**🤖 {modelo}**\n"
190
- f"📝 Respuesta: **{respuesta}**\n"
191
- f"📊 Confianza: {confianza:.2%}\n"
192
- )
193
- print(f"✅ Éxito con {modelo}")
194
-
195
- except Exception as e:
196
- print(f"❌ Error con {modelo}: {str(e)}")
197
- resultados.append(f"**{modelo}**: No disponible\n")
198
-
199
- if resultados:
200
- return "\n".join(resultados)
201
- return "❌ No se pudo procesar con modelos Layout QA"
202
 
203
- # ============= VISUAL DOCUMENT UNDERSTANDING CON MODELOS DE HF =============
204
- def analizar_documento_visual_hf(pdf_path):
205
- """Usa modelos multimodales de Hugging Face para entender documentos visualmente"""
206
-
207
- token = os.getenv("aa")
208
- if not token:
209
- return None, "❌ Error: Falta configurar HF_TOKEN"
210
-
211
- images = pdf_to_images(pdf_path)
212
- if not images:
213
- return None, "❌ No se pudo convertir el PDF"
214
-
215
- primera_pagina = images[0]
216
-
217
- # Modelos multimodales de Hugging Face para Document Understanding
218
- modelos_visuales = [
219
- "microsoft/trocr-large-printed",
220
- "Salesforce/blip-image-captioning-large",
221
- "nlpconnect/vit-gpt2-image-captioning"
222
  ]
223
 
224
- client = InferenceClient(token=token)
225
- resultados = []
226
-
227
- for modelo in modelos_visuales:
228
  try:
229
- print(f"\n🖼️ Probando Visual Document con: {modelo}")
 
230
 
231
- # Usar image-to-text para OCR y comprensión visual
232
- response = client.image_to_text(
233
- image=primera_pagina,
234
- model=modelo
 
 
 
 
235
  )
236
 
237
- texto_extraido = response if isinstance(response, str) else response.get('generated_text', str(response))
 
238
 
239
- resultados.append(f"**🤖 {modelo}**\n📝 Texto extraído:\n{texto_extraido}\n")
240
- print(f"✅ Éxito con {modelo}")
241
 
 
 
242
  except Exception as e:
243
  print(f"❌ Error con {modelo}: {str(e)}")
244
- resultados.append(f"**{modelo}**: Error\n")
245
-
246
- if resultados:
247
- return "\n".join(resultados), "✅ Procesado con modelos visuales"
248
 
249
- return None, "❌ No se pudo procesar visualmente"
250
 
251
- # ============= DOCUMENT RETRIEVAL - BÚSQUEDA EN DOCUMENTOS =============
252
- def buscar_en_documento(texto, consulta="información sobre el emisor"):
253
- """Usa modelos de embeddings para búsqueda semántica en documentos"""
254
-
255
- token = os.getenv("aa")
256
- if not token:
257
- return "❌ Error: Falta configurar HF_TOKEN"
258
-
259
- # Modelos de embeddings para búsqueda semántica
260
- modelos_retrieval = [
261
- "sentence-transformers/all-MiniLM-L6-v2",
262
- "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
263
  ]
264
 
265
- client = InferenceClient(token=token)
266
-
267
- # Dividir el texto en fragmentos
268
- fragmentos = [texto[i:i+500] for i in range(0, min(len(texto), 3000), 500)]
269
-
270
- resultados = []
271
 
272
- for modelo in modelos_retrieval:
273
  try:
274
- print(f"\n🔎 Probando Document Retrieval con: {modelo}")
275
 
276
- # Generar embedding de la consulta
277
- query_embedding = client.feature_extraction(
278
- text=consulta,
279
  model=modelo
280
  )
281
 
282
- # Buscar fragmentos más relevantes
283
- scores = []
284
- for i, frag in enumerate(fragmentos):
285
- try:
286
- frag_embedding = client.feature_extraction(
287
- text=frag,
288
- model=modelo
289
- )
290
- # Calcular similitud (simplificado)
291
- scores.append((i, frag))
292
- except:
293
- continue
294
 
295
- if scores:
296
- # Tomar los 2 fragmentos más relevantes
297
- top_frags = scores[:2]
298
- resultado_texto = "\n\n".join([f"**Fragmento {i+1}:**\n{frag[:300]}..." for i, frag in top_frags])
299
-
300
- resultados.append(
301
- f"**🤖 {modelo}**\n"
302
- f"📍 Fragmentos relevantes encontrados:\n{resultado_texto}\n"
303
- )
304
- print(f"✅ Éxito con {modelo}")
305
 
 
 
 
306
  except Exception as e:
307
- print(f"❌ Error con {modelo}: {str(e)}")
308
- resultados.append(f"**{modelo}**: Error\n")
309
 
310
- if resultados:
311
- return "\n".join(resultados)
312
- return "❌ No se pudo realizar búsqueda en el documento"
313
 
314
  # ============= ANALIZAR CON LLM Y CONVERTIR A JSON =============
315
  def analizar_y_convertir_json(texto):
@@ -639,6 +454,35 @@ with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
639
  gr.Markdown("### Subir Factura PDF")
640
  pdf_input = gr.File(label="Seleccionar factura PDF", file_types=[".pdf"], type="filepath")
641
  btn_extraer = gr.Button("🚀 Extraer Datos de la Factura", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  gr.Markdown("---")
643
  csv_output = gr.File(label="📥 Descargar CSV generado")
644
  gr.Markdown("---")
@@ -660,145 +504,135 @@ with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
660
  with gr.Tab("Más información"):
661
  resumen_tecnico = gr.Markdown(label="Estructura de datos y metadatos")
662
 
663
- # ============= TAB 2: CONSULTAS INTELIGENTES (TODO EN UNO) =============
664
- with gr.Tab("🤖 Consultas con IA"):
665
  gr.Markdown("""
666
- ### 💬 Pregunta lo que necesites sobre tu factura
667
- Los modelos de IA responden preguntas específicas sobre el contenido de la factura.
 
 
 
 
668
  """)
669
 
670
  with gr.Row():
671
  with gr.Column(scale=1):
672
  pregunta_ia = gr.Textbox(
673
- label="Tu pregunta sobre la factura",
674
- placeholder="Ejemplos: ¿Cuál es el total? ¿Quién es el emisor? ¿Cuál es el NIF?",
675
- value="¿Cuál es el total de la factura?",
676
- lines=3
677
  )
678
 
679
- gr.Markdown("#### Ejemplos de preguntas:")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  gr.Markdown("""
 
681
  - ¿Cuál es el total de la factura?
 
682
  - ¿Cuál es el número de factura?
683
- - ¿Quién es el emisor?
684
- - ¿Cuál es el NIF del emisor?
685
- - ¿Cuántos productos hay?
686
- - ¿Cuál es la fecha de emisión?
687
- """)
688
 
689
- btn_consulta_ia = gr.Button("🔍 Consultar", variant="primary", size="lg")
690
-
691
- with gr.Column(scale=2):
692
- gr.Markdown("### 📝 Respuestas de los modelos")
693
- resultado_consulta = gr.Markdown(label="Respuestas")
694
-
695
- gr.Markdown("---")
696
- gr.Markdown("""
697
- **Modelos utilizados:**
698
- - `deepset/roberta-base-squad2` - RoBERTa especializado en QA
699
- - `deepset/bert-base-cased-squad2` - BERT optimizado para preguntas
700
- - `distilbert-base-cased-distilled-squad` - DistilBERT eficiente
701
-
702
- Estos modelos están entrenados para extraer respuestas precisas del texto.
703
- """)
704
-
705
- # ============= TAB 3: BÚSQUEDA SEMÁNTICA =============
706
- with gr.Tab("🔎 Búsqueda Inteligente"):
707
- gr.Markdown("""
708
- ### 🎯 Encuentra información relevante en tu factura
709
- Búsqueda semántica que entiende el significado de tu consulta.
710
- """)
711
-
712
- with gr.Row():
713
- with gr.Column(scale=1):
714
- consulta_busqueda = gr.Textbox(
715
- label="¿Qué información buscas?",
716
- placeholder="Ejemplos: información del emisor, detalles de productos, información de pago",
717
- value="información sobre el emisor",
718
- lines=3
719
- )
720
 
721
- gr.Markdown("#### Ejemplos de búsquedas:")
722
- gr.Markdown("""
723
- - Información sobre el emisor
724
- - Detalles de productos o servicios
725
- - Información de pago
726
- - Datos del cliente
727
- - Fechas importantes
728
  """)
729
-
730
- btn_busqueda = gr.Button("🔎 Buscar", variant="primary", size="lg")
731
 
732
  with gr.Column(scale=2):
733
- gr.Markdown("### 📋 Fragmentos relevantes encontrados")
734
- resultado_busqueda = gr.Markdown(label="Resultados")
735
-
736
- gr.Markdown("---")
737
- gr.Markdown("""
738
- **Modelos de embeddings utilizados:**
739
- - `sentence-transformers/all-MiniLM-L6-v2` - Embeddings rápidos y precisos
740
- - `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` - Soporte multilingüe
741
-
742
- La búsqueda semántica encuentra información relevante aunque uses palabras diferentes.
743
- """)
744
-
745
- # ============= TAB 4: ANÁLISIS VISUAL (OPCIONAL) =============
746
- with gr.Tab("🖼️ Análisis Visual (Beta)"):
747
- gr.Markdown("""
748
- ### 📸 Análisis visual del documento
749
- **Nota:** Esta funcionalidad requiere dependencias adicionales (poppler-utils o PyMuPDF).
750
- """)
751
-
752
- with gr.Row():
753
- with gr.Column(scale=1):
754
- pregunta_visual = gr.Textbox(
755
- label="Pregunta sobre la imagen",
756
- placeholder="¿Qué información contiene la factura?",
757
- value="¿Qué información importante contiene esta factura?",
758
- lines=3
759
  )
760
 
761
- btn_visual = gr.Button("🖼️ Analizar Visualmente", variant="primary", size="lg")
762
-
763
  gr.Markdown("---")
764
- gr.Markdown("""
765
- ⚠️ **Requisitos:**
766
- - Poppler-utils instalado en el sistema
767
- - O PyMuPDF (fitz) como alternativa
 
 
768
 
769
- Si no funciona, usa las otras pestañas que trabajan con el texto.
 
 
 
 
770
  """)
771
-
772
- with gr.Column(scale=2):
773
- resultado_visual = gr.Markdown(label="Análisis visual")
774
-
775
- gr.Markdown("""
776
- **Modelos de visión utilizados:**
777
- - `dandelin/vilt-b32-finetuned-vqa` - Vision-and-Language Transformer
778
- - `Salesforce/blip-vqa-base` - BLIP para Visual QA
779
- - `microsoft/trocr-large-printed` - OCR avanzado
780
- """)
781
 
782
  gr.Markdown("---")
783
  gr.Markdown("""
784
- ### 📚 Guía rápida
 
 
 
 
 
785
 
786
- 1. **Extracción Automática:** Sube tu PDF y extrae todos los datos automáticamente
787
- 2. **Consultas con IA:** Haz preguntas específicas sobre la factura
788
- 3. **Búsqueda Inteligente:** Encuentra información relevante por tema
789
- 4. **Análisis Visual:** (Opcional) Analiza la imagen del documento
790
 
791
- 💡 **Tip:** Empieza por la pestaña "Extracción Automática" para procesar tu factura.
 
 
 
 
 
 
792
  """)
793
 
794
  # ============= CONECTAR EVENTOS =============
795
 
796
- # Extracción automática
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
  btn_extraer.click(
798
- fn=procesar_factura,
799
  inputs=[pdf_input],
800
  outputs=[texto_extraido, tabla_preview, csv_output, resumen_tecnico, info_util,
801
- datos_json_state, csv_file_state, pdf_path_state]
802
  )
803
 
804
  # Generar PDF
@@ -808,50 +642,24 @@ with gr.Blocks(title="Extractor de Facturas con IA Avanzada") as demo:
808
  outputs=[pdf_output, pdf_status]
809
  )
810
 
811
- # Consultas con IA (unificado Document QA)
812
- def ejecutar_consulta_ia(texto, pregunta):
813
  if not texto:
814
- return "❌ Por favor, procesa una factura primero en la pestaña 'Extracción Automática'"
815
- return analizar_con_document_qa(texto, pregunta)
816
-
817
- btn_consulta_ia.click(
818
- fn=ejecutar_consulta_ia,
819
- inputs=[texto_extraido, pregunta_ia],
820
- outputs=[resultado_consulta]
821
- )
822
-
823
- # Búsqueda semántica
824
- def ejecutar_busqueda_semantica(texto, consulta):
825
- if not texto:
826
- return "❌ Por favor, procesa una factura primero en la pestaña 'Extracción Automática'"
827
- return buscar_en_documento(texto, consulta)
828
-
829
- btn_busqueda.click(
830
- fn=ejecutar_busqueda_semantica,
831
- inputs=[texto_extraido, consulta_busqueda],
832
- outputs=[resultado_busqueda]
833
- )
834
-
835
- # Análisis visual (combinado VQA + Visual Document Understanding)
836
- def ejecutar_analisis_visual(pdf_path, pregunta):
837
- if not pdf_path:
838
- return "❌ Por favor, procesa una factura primero en la pestaña 'Extracción Automática'"
839
 
840
- # Intentar VQA primero
841
- resultado_vqa = analizar_con_vqa(pdf_path, pregunta)
842
 
843
- # Si VQA no funciona, intentar Visual Document Understanding
844
- if "No se pudo convertir" in resultado_vqa or "Error" in resultado_vqa:
845
- resultado_visual, status = analizar_documento_visual_hf(pdf_path)
846
- if resultado_visual:
847
- return f"{resultado_vqa}\n\n---\n\n### Análisis Visual Alternativo:\n\n{resultado_visual}"
848
 
849
- return resultado_vqa
 
850
 
851
- btn_visual.click(
852
- fn=ejecutar_analisis_visual,
853
- inputs=[pdf_path_state, pregunta_visual],
854
- outputs=[resultado_visual]
855
  )
856
 
857
  if __name__ == "__main__":
 
12
  from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
13
  from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
14
  from reportlab.lib.enums import TA_CENTER, TA_RIGHT, TA_LEFT
15
+ import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  # ============= EXTRAER TEXTO DEL PDF =============
18
  def extraer_texto_pdf(pdf_file):
 
25
  except Exception as e:
26
  return f"Error: {str(e)}"
27
 
28
+ # ============= ASISTENTE IA CONVERSACIONAL =============
29
+ def asistente_ia_factura(texto, pregunta_usuario):
30
+ """Asistente IA que explica conceptos, responde preguntas y da consejos sobre facturas"""
31
 
32
  token = os.getenv("aa")
33
  if not token:
34
+ return "❌ Error: Falta configurar HF_TOKEN en Settings → Secrets", None
 
 
 
 
 
35
 
36
+ texto_limpio = texto[:6000]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ prompt = f"""Eres un asistente experto en facturas y finanzas que ayuda a entender documentos comerciales.
 
 
39
 
40
+ TEXTO DE LA FACTURA:
41
+ {texto_limpio}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
+ PREGUNTA DEL USUARIO: {pregunta_usuario}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ INSTRUCCIONES:
46
+ 1. Responde de forma clara, amigable y profesional en español
47
+ 2. Si te preguntan sobre conceptos (IVA, base imponible, etc.), explícalos de manera sencilla
48
+ 3. Si te preguntan datos específicos, extráelos del texto de la factura
49
+ 4. Da consejos útiles cuando sea relevante (gestión, pagos, fiscalidad básica)
50
+ 5. Si no encuentras información específica en la factura, indícalo claramente
51
+ 6. Usa un lenguaje accesible para personas sin conocimientos técnicos
52
+ 7. Sé conciso pero completo (máximo 250 palabras)
53
+
54
+ Responde ahora:"""
55
+
56
+ modelos = [
57
+ "Qwen/Qwen2.5-72B-Instruct",
58
+ "meta-llama/Llama-3.2-3B-Instruct",
59
+ "mistralai/Mistral-Nemo-Instruct-2407"
 
 
 
 
60
  ]
61
 
62
+ for modelo in modelos:
 
 
 
63
  try:
64
+ print(f"\n🤖 Consultando con: {modelo}")
65
+ client = InferenceClient(token=token)
66
 
67
+ response = client.chat.completions.create(
68
+ model=modelo,
69
+ messages=[
70
+ {"role": "system", "content": "Eres un asistente experto en facturas, finanzas y contabilidad básica. Ayudas a las personas a entender sus documentos comerciales."},
71
+ {"role": "user", "content": prompt}
72
+ ],
73
+ max_tokens=800,
74
+ temperature=0.7
75
  )
76
 
77
+ respuesta = response.choices[0].message.content
78
+ print(f"✅ Respuesta obtenida con {modelo}")
79
 
80
+ # Generar audio de la respuesta
81
+ audio_path = generar_audio_respuesta(respuesta, client)
82
 
83
+ return respuesta, audio_path
84
+
85
  except Exception as e:
86
  print(f"❌ Error con {modelo}: {str(e)}")
87
+ continue
 
 
 
88
 
89
+ return "❌ No se pudo obtener respuesta del asistente IA", None
90
 
91
+ # ============= GENERAR AUDIO DE LA RESPUESTA =============
92
+ def generar_audio_respuesta(texto, client):
93
+ """Convierte la respuesta de texto a audio usando TTS de Hugging Face"""
94
+
95
+ modelos_tts = [
96
+ "espnet/kan-bayashi_ljspeech_vits",
97
+ "facebook/mms-tts-spa",
98
+ "microsoft/speecht5_tts"
 
 
 
 
99
  ]
100
 
101
+ # Limitar texto para TTS (máximo 500 caracteres)
102
+ texto_corto = texto[:500] if len(texto) > 500 else texto
 
 
 
 
103
 
104
+ for modelo in modelos_tts:
105
  try:
106
+ print(f"🔊 Generando audio con: {modelo}")
107
 
108
+ audio = client.text_to_speech(
109
+ text=texto_corto,
 
110
  model=modelo
111
  )
112
 
113
+ # Guardar audio
114
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
115
+ audio_path = f"respuesta_audio_{timestamp}.wav"
 
 
 
 
 
 
 
 
 
116
 
117
+ with open(audio_path, "wb") as f:
118
+ f.write(audio)
 
 
 
 
 
 
 
 
119
 
120
+ print(f"✅ Audio generado: {audio_path}")
121
+ return audio_path
122
+
123
  except Exception as e:
124
+ print(f"❌ Error generando audio con {modelo}: {str(e)}")
125
+ continue
126
 
127
+ return None
 
 
128
 
129
  # ============= ANALIZAR CON LLM Y CONVERTIR A JSON =============
130
  def analizar_y_convertir_json(texto):
 
454
  gr.Markdown("### Subir Factura PDF")
455
  pdf_input = gr.File(label="Seleccionar factura PDF", file_types=[".pdf"], type="filepath")
456
  btn_extraer = gr.Button("🚀 Extraer Datos de la Factura", variant="primary", size="lg")
457
+
458
+ # Indicador de carga para extracción
459
+ loading_extraccion = gr.HTML(visible=False, value="""
460
+ <div style="text-align: center; padding: 20px;">
461
+ <div class="spinner"></div>
462
+ <p style="margin-top: 10px; color: #2196F3; font-weight: bold;">
463
+ 🔄 Procesando tu factura...
464
+ </p>
465
+ <audio autoplay loop>
466
+ <source src="https://assets.mixkit.co/active_storage/sfx/2869/2869-preview.mp3" type="audio/mpeg">
467
+ </audio>
468
+ </div>
469
+ <style>
470
+ .spinner {
471
+ border: 4px solid #f3f3f3;
472
+ border-top: 4px solid #2196F3;
473
+ border-radius: 50%;
474
+ width: 40px;
475
+ height: 40px;
476
+ animation: spin 1s linear infinite;
477
+ margin: 0 auto;
478
+ }
479
+ @keyframes spin {
480
+ 0% { transform: rotate(0deg); }
481
+ 100% { transform: rotate(360deg); }
482
+ }
483
+ </style>
484
+ """)
485
+
486
  gr.Markdown("---")
487
  csv_output = gr.File(label="📥 Descargar CSV generado")
488
  gr.Markdown("---")
 
504
  with gr.Tab("Más información"):
505
  resumen_tecnico = gr.Markdown(label="Estructura de datos y metadatos")
506
 
507
+ # ============= TAB 2: ASISTENTE IA CON VOZ =============
508
+ with gr.Tab("🤖 Asistente IA con Voz"):
509
  gr.Markdown("""
510
+ # 💬 Pregúntale al Asistente IA sobre tu Factura
511
+ ### El asistente puede:
512
+ - ✅ Responder preguntas específicas sobre tu factura
513
+ - ✅ Explicar conceptos contables (IVA, base imponible, etc.)
514
+ - ✅ Dar consejos sobre gestión y pagos
515
+ - ✅ **Leer la respuesta en voz alta** 🔊
516
  """)
517
 
518
  with gr.Row():
519
  with gr.Column(scale=1):
520
  pregunta_ia = gr.Textbox(
521
+ label="💭 Tu pregunta o consulta",
522
+ placeholder="Escribe tu pregunta aquí...",
523
+ value="¿Cuál es el total de esta factura y cuándo debería pagarla?",
524
+ lines=4
525
  )
526
 
527
+ btn_consulta_ia = gr.Button("🎤 Consultar y Escuchar Respuesta", variant="primary", size="lg")
528
+
529
+ # Indicador de carga para IA
530
+ loading_ia = gr.HTML(visible=False, value="""
531
+ <div style="text-align: center; padding: 20px;">
532
+ <div class="spinner-ia"></div>
533
+ <p style="margin-top: 10px; color: #9C27B0; font-weight: bold;">
534
+ 🧠 El asistente IA está pensando...
535
+ </p>
536
+ <audio autoplay loop>
537
+ <source src="https://assets.mixkit.co/active_storage/sfx/2571/2571-preview.mp3" type="audio/mpeg">
538
+ </audio>
539
+ </div>
540
+ <style>
541
+ .spinner-ia {
542
+ border: 4px solid #f3f3f3;
543
+ border-top: 4px solid #9C27B0;
544
+ border-radius: 50%;
545
+ width: 50px;
546
+ height: 50px;
547
+ animation: spin 0.8s linear infinite;
548
+ margin: 0 auto;
549
+ }
550
+ </style>
551
+ """)
552
+
553
+ gr.Markdown("---")
554
+ gr.Markdown("#### 💡 Ejemplos de preguntas:")
555
  gr.Markdown("""
556
+ **Preguntas sobre datos:**
557
  - ¿Cuál es el total de la factura?
558
+ - ¿Quién emitió esta factura?
559
  - ¿Cuál es el número de factura?
 
 
 
 
 
560
 
561
+ **Explicación de conceptos:**
562
+ - ¿Qué es la base imponible?
563
+ - ¿Por qué se aplica IVA?
564
+ - ¿Qué significa "exento de IVA"?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
 
566
+ **Consejos y recomendaciones:**
567
+ - ¿Cuándo debería pagar esta factura?
568
+ - ¿Qué debo revisar en esta factura?
569
+ - ¿Cómo organizo mis facturas?
 
 
 
570
  """)
 
 
571
 
572
  with gr.Column(scale=2):
573
+ gr.Markdown("### 📝 Respuesta del Asistente")
574
+ resultado_ia = gr.Markdown(
575
+ label="Respuesta",
576
+ value="*Haz una pregunta y el asistente te responderá aquí...*"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  )
578
 
 
 
579
  gr.Markdown("---")
580
+ gr.Markdown("### 🔊 Escucha la Respuesta")
581
+ audio_respuesta = gr.Audio(
582
+ label="Audio de la respuesta",
583
+ type="filepath",
584
+ visible=True
585
+ )
586
 
587
+ gr.Markdown("""
588
+ 💡 **Tip:** El asistente genera un archivo de audio que puedes:
589
+ - ▶️ Reproducir directamente aquí
590
+ - 📥 Descargar para escucharlo después
591
+ - 🔄 Hacer nuevas preguntas cuando quieras
592
  """)
 
 
 
 
 
 
 
 
 
 
593
 
594
  gr.Markdown("---")
595
  gr.Markdown("""
596
+ ### 📚 Guía rápida de uso
597
+
598
+ 1. **📄 Extracción Automática:** Sube tu PDF y extrae todos los datos automáticamente
599
+ 2. **🤖 Asistente IA con Voz:** Haz preguntas y escucha las respuestas en audio
600
+
601
+ ---
602
 
603
+ ### 🎯 Características del Asistente IA:
 
 
 
604
 
605
+ - **🧠 Inteligente:** Entiende tu pregunta en lenguaje natural
606
+ - **📖 Educativo:** Explica conceptos contables de forma sencilla
607
+ - **💡 Útil:** Da consejos prácticos sobre gestión de facturas
608
+ - **🔊 Accesible:** Convierte la respuesta a audio automáticamente
609
+ - **⚡ Rápido:** Responde en segundos
610
+
611
+ 💡 **Empieza por la pestaña "Extracción Automática" para procesar tu factura.**
612
  """)
613
 
614
  # ============= CONECTAR EVENTOS =============
615
 
616
+ # Extracción automática con loading
617
+ def procesar_con_loading(pdf_file):
618
+ if pdf_file is None:
619
+ return "", None, None, "", "", None, None, None, gr.update(visible=False)
620
+
621
+ # Mostrar loading
622
+ yield "", None, None, "", "", None, None, None, gr.update(visible=True)
623
+
624
+ # Procesar factura
625
+ time.sleep(0.5) # Pequeña pausa para que se vea el loading
626
+ resultado = procesar_factura(pdf_file)
627
+
628
+ # Ocultar loading y mostrar resultados
629
+ yield (*resultado, gr.update(visible=False))
630
+
631
  btn_extraer.click(
632
+ fn=procesar_con_loading,
633
  inputs=[pdf_input],
634
  outputs=[texto_extraido, tabla_preview, csv_output, resumen_tecnico, info_util,
635
+ datos_json_state, csv_file_state, pdf_path_state, loading_extraccion]
636
  )
637
 
638
  # Generar PDF
 
642
  outputs=[pdf_output, pdf_status]
643
  )
644
 
645
+ # Asistente IA con voz y loading
646
+ def consultar_ia_con_loading(texto, pregunta):
647
  if not texto:
648
+ return "❌ Por favor, procesa una factura primero en la pestaña 'Extracción Automática'", None, gr.update(visible=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
+ # Mostrar loading
651
+ yield "🔄 Consultando al asistente IA...", None, gr.update(visible=True)
652
 
653
+ # Procesar consulta
654
+ respuesta, audio = asistente_ia_factura(texto, pregunta)
 
 
 
655
 
656
+ # Ocultar loading y mostrar resultados
657
+ yield respuesta, audio, gr.update(visible=False)
658
 
659
+ btn_consulta_ia.click(
660
+ fn=consultar_ia_con_loading,
661
+ inputs=[texto_extraido, pregunta_ia],
662
+ outputs=[resultado_ia, audio_respuesta, loading_ia]
663
  )
664
 
665
  if __name__ == "__main__":