Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
|
3 |
+
import whisper
|
4 |
+
|
5 |
+
import tempfile
|
6 |
+
|
7 |
+
import gradio as gr
|
8 |
+
|
9 |
+
from pydub import AudioSegment
|
10 |
+
|
11 |
+
import fitz # PyMuPDF para manejar PDFs
|
12 |
+
|
13 |
+
import docx # Para manejar archivos .docx
|
14 |
+
|
15 |
+
import pandas as pd # Para manejar archivos .xlsx y .csv
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
# Configura tu clave API de OpenAI
|
20 |
+
|
21 |
+
openai.api_key = "sk-proj-oa7IsWQdSibP9urkzepFT3BlbkFJ9L9tXiDZpjgPikmAhtQP"
|
22 |
+
|
23 |
+
|
24 |
+
|
25 |
+
# Cargar el modelo Whisper de mayor calidad
|
26 |
+
|
27 |
+
model = whisper.load_model("large")
|
28 |
+
|
29 |
+
|
30 |
+
|
31 |
+
def preprocess_audio(audio_file):
|
32 |
+
|
33 |
+
"""Preprocesa el archivo de audio para mejorar la calidad."""
|
34 |
+
|
35 |
+
try:
|
36 |
+
|
37 |
+
audio = AudioSegment.from_file(audio_file)
|
38 |
+
|
39 |
+
# Normaliza el audio al -20 dBFS
|
40 |
+
|
41 |
+
audio = audio.apply_gain(-audio.dBFS + (-20))
|
42 |
+
|
43 |
+
# Exporta el audio procesado a un archivo temporal
|
44 |
+
|
45 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file:
|
46 |
+
|
47 |
+
audio.export(temp_file.name, format="mp3")
|
48 |
+
|
49 |
+
return temp_file.name
|
50 |
+
|
51 |
+
except Exception as e:
|
52 |
+
|
53 |
+
return f"Error al preprocesar el archivo de audio: {str(e)}"
|
54 |
+
|
55 |
+
|
56 |
+
|
57 |
+
def transcribir_audio(audio_file):
|
58 |
+
|
59 |
+
"""Transcribe un archivo de audio."""
|
60 |
+
|
61 |
+
try:
|
62 |
+
|
63 |
+
if isinstance(audio_file, str):
|
64 |
+
|
65 |
+
archivo_path = preprocess_audio(audio_file)
|
66 |
+
|
67 |
+
else:
|
68 |
+
|
69 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_file:
|
70 |
+
|
71 |
+
temp_file.write(audio_file.read())
|
72 |
+
|
73 |
+
temp_file.flush()
|
74 |
+
|
75 |
+
archivo_path = preprocess_audio(temp_file.name)
|
76 |
+
|
77 |
+
resultado = model.transcribe(archivo_path)
|
78 |
+
|
79 |
+
return resultado.get("text", "Error en la transcripci贸n")
|
80 |
+
|
81 |
+
except Exception as e:
|
82 |
+
|
83 |
+
return f"Error al procesar el archivo de audio: {str(e)}"
|
84 |
+
|
85 |
+
|
86 |
+
|
87 |
+
def leer_documento(documento_path):
|
88 |
+
|
89 |
+
"""Lee el contenido de un documento PDF, DOCX, XLSX o CSV."""
|
90 |
+
|
91 |
+
try:
|
92 |
+
|
93 |
+
# Identificar el tipo de archivo
|
94 |
+
|
95 |
+
if documento_path.endswith(".pdf"):
|
96 |
+
|
97 |
+
doc = fitz.open(documento_path)
|
98 |
+
|
99 |
+
texto_completo = ""
|
100 |
+
|
101 |
+
for pagina in doc:
|
102 |
+
|
103 |
+
texto_completo += pagina.get_text()
|
104 |
+
|
105 |
+
return texto_completo
|
106 |
+
|
107 |
+
elif documento_path.endswith(".docx"):
|
108 |
+
|
109 |
+
doc = docx.Document(documento_path)
|
110 |
+
|
111 |
+
texto_completo = "\n".join([parrafo.text for parrafo in doc.paragraphs])
|
112 |
+
|
113 |
+
return texto_completo
|
114 |
+
|
115 |
+
elif documento_path.endswith(".xlsx"):
|
116 |
+
|
117 |
+
df = pd.read_excel(documento_path)
|
118 |
+
|
119 |
+
texto_completo = df.to_string()
|
120 |
+
|
121 |
+
return texto_completo
|
122 |
+
|
123 |
+
elif documento_path.endswith(".csv"):
|
124 |
+
|
125 |
+
df = pd.read_csv(documento_path)
|
126 |
+
|
127 |
+
texto_completo = df.to_string()
|
128 |
+
|
129 |
+
return texto_completo
|
130 |
+
|
131 |
+
else:
|
132 |
+
|
133 |
+
return "Tipo de archivo no soportado. Por favor suba un documento PDF, DOCX, XLSX o CSV."
|
134 |
+
|
135 |
+
except Exception as e:
|
136 |
+
|
137 |
+
return f"Error al leer el documento: {str(e)}"
|
138 |
+
|
139 |
+
|
140 |
+
|
141 |
+
def generar_noticia(instrucciones, hechos, tama帽o, tono, *args):
|
142 |
+
|
143 |
+
"""Genera una noticia a partir de instrucciones, hechos y transcripciones."""
|
144 |
+
|
145 |
+
base_de_conocimiento = {
|
146 |
+
|
147 |
+
"instrucciones": instrucciones,
|
148 |
+
|
149 |
+
"hechos": hechos,
|
150 |
+
|
151 |
+
"contenido_documentos": [],
|
152 |
+
|
153 |
+
"audio_data": []
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
|
158 |
+
|
159 |
+
# Recolecta los documentos y el audio desde los argumentos
|
160 |
+
|
161 |
+
num_audios = 5 * 3 # 5 audios * 3 campos (audio, nombre, cargo)
|
162 |
+
|
163 |
+
audios = args[:num_audios]
|
164 |
+
|
165 |
+
documentos = args[num_audios:]
|
166 |
+
|
167 |
+
|
168 |
+
|
169 |
+
# Leer el contenido de los documentos si se han subido
|
170 |
+
|
171 |
+
for documento in documentos:
|
172 |
+
|
173 |
+
if documento is not None:
|
174 |
+
|
175 |
+
contenido_doc = leer_documento(documento.name)
|
176 |
+
|
177 |
+
print(f"Contenido del documento {documento.name}: {contenido_doc}")
|
178 |
+
|
179 |
+
base_de_conocimiento["contenido_documentos"].append(contenido_doc)
|
180 |
+
|
181 |
+
|
182 |
+
|
183 |
+
# Recolecta datos de cada archivo de audio
|
184 |
+
|
185 |
+
for i in range(0, len(audios), 3):
|
186 |
+
|
187 |
+
audio_file, nombre, cargo = audios[i:i+3]
|
188 |
+
|
189 |
+
if audio_file is not None:
|
190 |
+
|
191 |
+
base_de_conocimiento["audio_data"].append({"audio": audio_file, "nombre": nombre, "cargo": cargo})
|
192 |
+
|
193 |
+
|
194 |
+
|
195 |
+
transcripciones_texto = ""
|
196 |
+
|
197 |
+
transcripciones_brutas = ""
|
198 |
+
|
199 |
+
total_citas_directas = 0
|
200 |
+
|
201 |
+
|
202 |
+
|
203 |
+
# Transcribe y compila las transcripciones
|
204 |
+
|
205 |
+
for idx, data in enumerate(base_de_conocimiento["audio_data"]):
|
206 |
+
|
207 |
+
if data["audio"] is not None:
|
208 |
+
|
209 |
+
transcripcion = transcribir_audio(data["audio"])
|
210 |
+
|
211 |
+
transcripcion_texto = f'"{transcripcion}" - {data["nombre"]}, {data["cargo"]}'
|
212 |
+
|
213 |
+
transcripcion_bruta = f'[Audio {idx + 1}]: "{transcripcion}" - {data["nombre"]}, {data["cargo"]}'
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
# Decidir si usar cita directa o indirecta
|
218 |
+
|
219 |
+
if total_citas_directas < len(base_de_conocimiento["audio_data"]) * 0.8:
|
220 |
+
|
221 |
+
transcripciones_texto += transcripcion_texto + "\n"
|
222 |
+
|
223 |
+
total_citas_directas += 1
|
224 |
+
|
225 |
+
else:
|
226 |
+
|
227 |
+
transcripciones_texto += f'{data["nombre"]} mencion贸 que {transcripcion}' + "\n"
|
228 |
+
|
229 |
+
|
230 |
+
|
231 |
+
transcripciones_brutas += transcripcion_bruta + "\n\n"
|
232 |
+
|
233 |
+
|
234 |
+
|
235 |
+
print(f"Transcripci贸n bruta [Audio {idx + 1}]: {transcripcion_bruta}")
|
236 |
+
|
237 |
+
print(f"Transcripciones brutas acumuladas: {transcripciones_brutas}")
|
238 |
+
|
239 |
+
|
240 |
+
|
241 |
+
contenido_documentos = "\n\n".join(base_de_conocimiento["contenido_documentos"])
|
242 |
+
|
243 |
+
|
244 |
+
|
245 |
+
# Prompt adicional para instrucciones internas
|
246 |
+
|
247 |
+
prompt_interno = """
|
248 |
+
|
249 |
+
Instrucciones para el modelo:
|
250 |
+
|
251 |
+
- Aseg煤rate de que al menos el 80% de las citas sean directas y est茅n entrecomilladas.
|
252 |
+
|
253 |
+
- El 20% restante puede ser citas indirectas.
|
254 |
+
|
255 |
+
- No inventes informaci贸n nueva.
|
256 |
+
|
257 |
+
- S茅 riguroso con los hechos proporcionados.
|
258 |
+
|
259 |
+
- Al procesar los documentos cargados, extrae y resalta citas importantes y testimonios textuales de las fuentes.
|
260 |
+
|
261 |
+
- Al procesar los documentos cargados, extrae y resalta cifras clave.
|
262 |
+
|
263 |
+
"""
|
264 |
+
|
265 |
+
|
266 |
+
|
267 |
+
# Compila el prompt para OpenAI
|
268 |
+
|
269 |
+
prompt = f"""
|
270 |
+
|
271 |
+
{prompt_interno}
|
272 |
+
|
273 |
+
Escribe una noticia con la siguiente informaci贸n, incluyendo un t铆tulo, un sumario de 20 palabras, y el cuerpo del contenido cuyo tama帽o es {tama帽o} palabras. El tono debe ser {tono}.
|
274 |
+
|
275 |
+
Instrucciones: {base_de_conocimiento["instrucciones"]}
|
276 |
+
|
277 |
+
Hechos: {base_de_conocimiento["hechos"]}
|
278 |
+
|
279 |
+
Contenido adicional de los documentos: {contenido_documentos}
|
280 |
+
|
281 |
+
Utiliza las siguientes transcripciones como citas directas e indirectas (sin cambiar ni inventar contenido):
|
282 |
+
|
283 |
+
{transcripciones_texto}
|
284 |
+
|
285 |
+
"""
|
286 |
+
|
287 |
+
|
288 |
+
|
289 |
+
try:
|
290 |
+
|
291 |
+
respuesta = openai.ChatCompletion.create(
|
292 |
+
|
293 |
+
model="gpt-3.5-turbo",
|
294 |
+
|
295 |
+
messages=[{"role": "user", "content": prompt}],
|
296 |
+
|
297 |
+
temperature=0.1 # Bajamos la temperatura para mayor rigurosidad
|
298 |
+
|
299 |
+
)
|
300 |
+
|
301 |
+
noticia = respuesta['choices'][0]['message']['content']
|
302 |
+
|
303 |
+
return noticia, transcripciones_brutas
|
304 |
+
|
305 |
+
except Exception as e:
|
306 |
+
|
307 |
+
return f"Error al generar la noticia: {str(e)}", ""
|
308 |
+
|
309 |
+
|
310 |
+
|
311 |
+
with gr.Blocks() as demo:
|
312 |
+
|
313 |
+
gr.Markdown("## Chatbot de noticias")
|
314 |
+
|
315 |
+
with gr.Row():
|
316 |
+
|
317 |
+
with gr.Column(scale=2):
|
318 |
+
|
319 |
+
instrucciones = gr.Textbox(label="Instrucciones para la noticia", lines=2)
|
320 |
+
|
321 |
+
hechos = gr.Textbox(label="Describe los hechos de la noticia", lines=4)
|
322 |
+
|
323 |
+
tama帽o = gr.Number(label="Tama帽o del cuerpo de la noticia (en palabras)", value=100)
|
324 |
+
|
325 |
+
tono = gr.Dropdown(label="Tono de la noticia", choices=["serio", "neutral", "divertido"], value="neutral")
|
326 |
+
|
327 |
+
with gr.Column(scale=3):
|
328 |
+
|
329 |
+
inputs_list = [instrucciones, hechos, tama帽o, tono]
|
330 |
+
|
331 |
+
with gr.Tabs():
|
332 |
+
|
333 |
+
for i in range(1, 6):
|
334 |
+
|
335 |
+
with gr.TabItem(f"Audio {i}"):
|
336 |
+
|
337 |
+
audio = gr.Audio(type="filepath", label=f"Audio {i}")
|
338 |
+
|
339 |
+
nombre = gr.Textbox(label="Nombre", scale=1)
|
340 |
+
|
341 |
+
cargo = gr.Textbox(label="Cargo", scale=1)
|
342 |
+
|
343 |
+
inputs_list.extend([audio, nombre, cargo])
|
344 |
+
|
345 |
+
for i in range(1, 6):
|
346 |
+
|
347 |
+
with gr.TabItem(f"Documento {i}"):
|
348 |
+
|
349 |
+
documento = gr.File(label=f"Documento {i}", type="filepath", file_count="single")
|
350 |
+
|
351 |
+
inputs_list.append(documento)
|
352 |
+
|
353 |
+
|
354 |
+
|
355 |
+
with gr.Row():
|
356 |
+
|
357 |
+
generar = gr.Button("Generar noticia")
|
358 |
+
|
359 |
+
with gr.Row():
|
360 |
+
|
361 |
+
noticia_output = gr.Textbox(label="Noticia generada", lines=20)
|
362 |
+
|
363 |
+
with gr.Row():
|
364 |
+
|
365 |
+
transcripciones_output = gr.Textbox(label="Transcripciones brutas de los audios", lines=10)
|
366 |
+
|
367 |
+
|
368 |
+
|
369 |
+
generar.click(fn=generar_noticia, inputs=inputs_list, outputs=[noticia_output, transcripciones_output])
|
370 |
+
|
371 |
+
|
372 |
+
|
373 |
+
demo.launch(share=True)
|