Josedcape commited on
Commit
a3b6cdc
·
verified ·
1 Parent(s): 5cb10eb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +583 -271
app.py CHANGED
@@ -1,31 +1,26 @@
1
- import streamlit as st
2
  import os
3
- import openai
4
- from google.cloud import texttospeech
5
- from utils.data_manager import (
6
- extraer_texto_pdf,
7
- preprocesar_texto,
8
- obtener_respuesta,
9
- flujo_laboratorio,
10
- flujo_insumos,
11
- buscar_datos_guardados,
12
- generar_notificaciones_pendientes,
13
- flujo_presupuestos,
14
- )
15
- from docx import Document
16
- import io
17
  import tempfile
 
18
  from dotenv import load_dotenv
19
- from flask import Flask, request, jsonify
20
- import threading
21
- from google.oauth2.service_account import Credentials
22
- from googleapiclient.discovery import build
23
- from datetime import datetime, timedelta
 
 
 
 
 
 
 
 
24
 
25
  # Cargar las claves API desde el archivo .env
26
  load_dotenv()
27
  openai_api_key = os.getenv("OPENAI_API_KEY")
28
- google_credentials = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
 
29
 
30
  # Verifica que las claves API están configuradas
31
  if not openai_api_key:
@@ -33,29 +28,441 @@ if not openai_api_key:
33
  else:
34
  openai.api_key = openai_api_key
35
 
36
- if not google_credentials:
37
- st.error("No Google credentials provided. Please set your credentials in the .env file.")
38
-
39
- # Crear la aplicación Flask
40
- app = Flask(__name__)
41
-
42
- @app.route('/process_audio', methods=['POST'])
43
- def process_audio():
44
- data = request.json
45
- transcription = data['transcription']
46
- return jsonify({'transcription': transcription})
47
-
48
- def run_flask_app():
49
- app.run(port=5000, debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Ejecutar Flask en un hilo separado
52
- flask_thread = threading.Thread(target=run_flask_app)
53
- flask_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- def inicializar_estado():
56
- """Inicializa el estado de la sesión si no está ya inicializado."""
 
 
57
  if 'modelo' not in st.session_state:
58
- st.session_state['modelo'] = "gpt-3.5-turbo-16k"
59
  if 'temperatura' not in st.session_state:
60
  st.session_state['temperatura'] = 0.5
61
  if 'mensajes_chat' not in st.session_state:
@@ -67,11 +474,13 @@ def inicializar_estado():
67
  if 'video_estado' not in st.session_state:
68
  st.session_state['video_estado'] = 'paused'
69
  if 'assistant_id' not in st.session_state:
70
- st.session_state['assistant_id'] = "asst_4ZYvBvf4IUVQPjnugSZGLdV2"
 
 
 
 
71
 
72
- inicializar_estado()
73
-
74
- def barra_lateral():
75
  ruta_logo = os.path.join("assets", "Logo Omardent.png")
76
  if os.path.exists(ruta_logo):
77
  st.sidebar.image(ruta_logo, use_column_width=True)
@@ -96,147 +505,10 @@ def barra_lateral():
96
  step=0.1,
97
  key='temperatura_slider' # Clave única
98
  )
99
- st.sidebar.text_input("Assistant ID", key="assistant_id", help="Introduce el Assistant ID del playground de OpenAI")
100
-
101
- def mostrar_mensajes_chat():
102
- for mensaje in st.session_state['mensajes_chat']:
103
- with st.chat_message(mensaje["role"]):
104
- st.markdown(mensaje["content"])
105
-
106
- def manejar_pregunta_usuario(pregunta_usuario, archivo_pdf=None):
107
- st.session_state['mensajes_chat'].append({"role": "user", "content": pregunta_usuario})
108
- with st.chat_message("user"):
109
- st.markdown(pregunta_usuario)
110
-
111
- texto_preprocesado = ""
112
- if archivo_pdf:
113
- texto_pdf = extraer_texto_pdf(archivo_pdf)
114
- texto_preprocesado = preprocesar_texto(texto_pdf)
115
-
116
- respuesta = obtener_respuesta(
117
- pregunta_usuario,
118
- texto_preprocesado,
119
- st.session_state['modelo'],
120
- st.session_state['temperatura'],
121
- st.session_state.get('assistant_id', '')
122
- )
123
-
124
- # Consulta de Google Calendar
125
- if "cita" in pregunta_usuario.lower():
126
- respuesta += "\n\n" + consultar_google_calendar(pregunta_usuario)
127
 
128
- st.session_state['mensajes_chat'].append({"role": "assistant", "content": respuesta})
129
- with st.chat_message("assistant"):
130
- st.markdown(respuesta)
131
-
132
- # Convertir la respuesta en voz
133
- client = texttospeech.TextToSpeechClient()
134
- synthesis_input = texttospeech.SynthesisInput(text=respuesta)
135
- voice = texttospeech.VoiceSelectionParams(language_code="es-ES", ssml_gender=texttospeech.SsmlVoiceGender.FEMALE)
136
- audio_config = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.MP3)
137
- response = client.synthesize_speech(input=synthesis_input, voice=voice, audio_config=audio_config)
138
-
139
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
140
- tmp_file.write(response.audio_content)
141
- st.audio(tmp_file.name)
142
-
143
- # Reproducir el video solo cuando el chat está activo
144
- st.session_state['video_estado'] = 'playing'
145
-
146
- def capturar_voz():
147
- st.markdown(
148
- """
149
- <style>
150
- .assistant-button {
151
- display: flex;
152
- align-items: center;
153
- justify-content: center;
154
- background-color: #4CAF50;
155
- color: white;
156
- padding: 10px;
157
- border: none;
158
- border-radius: 5px;
159
- cursor: pointer;
160
- font-size: 16px;
161
- margin-top: 10px;
162
- }
163
- .assistant-button img {
164
- margin-right: 10px;
165
- }
166
- </style>
167
- <button class="assistant-button" onclick="startRecording()">
168
- <img src='https://img2.gratispng.com/20180808/cxq/kisspng-robotics-science-computer-icons-robot-technology-robo-to-logo-svg-png-icon-free-download-45527-5b6baa46a5e322.4713113715337825986795.jpg' alt='icon' width='20' height='20'/>
169
- Capturar Voz
170
- </button>
171
- <script>
172
- function startRecording() {
173
- const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
174
- recognition.lang = 'es-ES';
175
- recognition.interimResults = false;
176
- recognition.maxAlternatives = 1;
177
- recognition.start();
178
- recognition.onresult = (event) => {
179
- const lastResult = event.results.length - 1;
180
- const text = event.results[lastResult][0].transcript;
181
- const customEvent = new CustomEvent('audioTranscription', { detail: text });
182
- document.dispatchEvent(customEvent);
183
- };
184
- recognition.onspeechend = () => {
185
- recognition.stop();
186
- };
187
- recognition.onerror = (event) => {
188
- console.error(event.error);
189
- };
190
- }
191
- document.addEventListener('audioTranscription', (event) => {
192
- const transcription = event.detail;
193
- document.querySelector("input[name='unique_chat_input_key']").value = transcription;
194
- // También puedes actualizar el estado de Streamlit aquí si es necesario
195
- fetch('/process_audio', {
196
- method: 'POST',
197
- headers: {
198
- 'Content-Type': 'application/json'
199
- },
200
- body: JSON.stringify({ transcription })
201
- }).then(response => response.json())
202
- .then(data => {
203
- // Manejo de la respuesta de Flask si es necesario
204
- });
205
- });
206
- </script>
207
- """,
208
- unsafe_allow_html=True
209
- )
210
-
211
- def consultar_google_calendar(pregunta):
212
- SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
213
- creds = None
214
-
215
- if not os.path.exists('service_account.json'):
216
- return 'Error: archivo service_account.json no encontrado.'
217
-
218
- creds = Credentials.from_service_account_file('service_account.json', scopes=SCOPES)
219
-
220
- service = build('calendar', 'v3', credentials=creds)
221
-
222
- now = datetime.utcnow().isoformat() + 'Z'
223
- events_result = service.events().list(
224
- calendarId='primary', timeMin=now,
225
- maxResults=10, singleEvents=True,
226
- orderBy='startTime').execute()
227
- events = events_result.get('items', [])
228
-
229
- if not events:
230
- return 'No hay próximas citas encontradas.'
231
- else:
232
- eventos = []
233
- for event in events:
234
- start = event['start'].get('dateTime', event['start'].get('date'))
235
- eventos.append(f"{event['summary']} at {start}")
236
- return '\n'.join(eventos)
237
-
238
- def mostrar_paginas():
239
- st.sidebar.title("Navegación Lateral")
240
  lateral_page = st.sidebar.radio("Ir a", ["Página Principal", "Gestión de Trabajos", "Gestión de Insumos", "Registro de Radiografías", "Buscar Datos", "Notificaciones", "Recomendaciones", "Asistente de Presupuestos", "Comunicación", "Asistente de Agendamiento"])
241
 
242
  top_page = st.selectbox("Navegación Superior", ["Página Principal", "Galatea-Asistente"])
@@ -257,7 +529,7 @@ def mostrar_paginas():
257
  elif lateral_page == "Notificaciones":
258
  generar_notificaciones_pendientes()
259
  elif lateral_page == "Recomendaciones":
260
- show_document_modification() # Implementar según sea necesario
261
  elif lateral_page == "Asistente de Presupuestos":
262
  flujo_presupuestos()
263
  elif lateral_page == "Comunicación":
@@ -348,8 +620,8 @@ def mostrar_galatea_asistente():
348
  }
349
  </style>
350
  <div id="video-container">
351
- <video id="background-video" {st.session_state['video_estado']} loop muted>
352
- <source src="./Welcome to.mp4" type="video/mp4">
353
  </video>
354
  </div>
355
  <div id="chat-container">
@@ -372,98 +644,138 @@ def mostrar_galatea_asistente():
372
  else:
373
  st.warning("No se ha cargado ninguna imagen. Por favor, carga una imagen en la página principal.")
374
 
375
- def show_document_modification():
376
- st.title("✍️ IA para Modificación de Documentos")
377
-
378
- uploaded_file = st.file_uploader("Sube un documento (PDF o DOCX)", type=['pdf', 'docx'])
379
-
380
- if uploaded_file:
381
- if uploaded_file.type == "application/pdf":
382
- texto_original = extraer_texto_pdf(uploaded_file)
383
- st.text_area("Contenido del Documento Original", texto_original, height=300)
384
- elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
385
- documento = Document(uploaded_file)
386
- texto_original = "\n".join([para.text for para in documento.paragraphs])
387
- st.text_area("Contenido del Documento Original", texto_original, height=300)
388
-
389
- st.markdown("### Modificar Documento")
390
-
391
- receptor = st.text_input("Modificar Para (Nombre del Receptor):")
392
- notas_adicionales = st.text_area("Notas Adicionales:", height=100)
393
-
394
- if st.button("Generar Documento Modificado"):
395
- contenido_modificado = modificar_documento(texto_original, receptor, notas_adicionales)
396
- st.text_area("Contenido del Documento Modificado", contenido_modificado, height=300)
397
-
398
- # Descargar documento modificado
399
- if uploaded_file.type == "application/pdf":
400
- pdf_modificado = modificar_pdf(uploaded_file, contenido_modificado)
401
- st.download_button(
402
- label="📥 Descargar PDF Modificado",
403
- data=pdf_modificado,
404
- file_name="documento_modificado.pdf",
405
- mime="application/pdf"
406
- )
407
- elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
408
- docx_modificado = modificar_docx(contenido_modificado)
409
- st.download_button(
410
- label="📥 Descargar DOCX Modificado",
411
- data=docx_modificado,
412
- file_name="documento_modificado.docx",
413
- mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
414
- )
415
-
416
- def modificar_documento(texto, receptor, notas):
417
- """Modifica el contenido del documento basado en el receptor y notas adicionales."""
418
- prompt = f"Modifica el siguiente texto para que esté dirigido a {receptor} y añade las siguientes notas: {notas}.\n\nTexto:\n{texto}"
419
- response = openai.ChatCompletion.create(
420
- model="gpt-3.5-turbo", # o "gpt-4" si estás usando ese modelo
421
- messages=[
422
- {"role": "system", "content": "Eres un asistente útil."},
423
- {"role": "user", "content": prompt}
424
- ]
425
- )
426
- return response.choices[0].message['content']
427
 
428
- def modificar_pdf(pdf_file, nuevo_contenido):
429
- """Modifica el contenido de un PDF."""
430
- reader = PdfReader(pdf_file)
431
- writer = PdfWriter()
432
 
433
- # Añadir el contenido modificado a una nueva página del PDF
434
- for page in reader.pages:
435
- writer.add_page(page)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
436
 
437
- nueva_pagina = writer.add_blank_page(width=reader.pages[0].mediabox.width, height=reader.pages[0].mediabox.height)
438
- nueva_pagina.insert_text(nuevo_contenido)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
 
440
- pdf_output = io.BytesIO()
441
- writer.write(pdf_output)
442
- pdf_output.seek(0)
443
 
444
- return pdf_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
 
446
- def modificar_docx(nuevo_contenido):
447
- """Modifica el contenido de un DOCX."""
448
- doc = Document()
449
- for line in nuevo_contenido.split("\n"):
450
- doc.add_paragraph(line)
451
 
452
- docx_output = io.BytesIO()
453
- doc.save(docx_output)
454
- docx_output.seek(0)
 
 
 
455
 
456
- return docx_output
 
 
457
 
458
- def main():
459
- inicializar_estado()
460
- barra_lateral()
461
- mostrar_paginas()
462
-
463
- if st.session_state['imagen_asistente'] is None:
464
- imagen_asistente = st.file_uploader("Cargar Imagen del Asistente", type=['jpg', 'jpeg', 'png'], key='imagen_asistente')
465
- if imagen_asistente:
466
- st.session_state['imagen_asistente'] = imagen_asistente
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
  if __name__ == "__main__":
469
  main()
 
 
1
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import tempfile
3
+ import openai
4
  from dotenv import load_dotenv
5
+ import PyPDF2
6
+ import nltk
7
+ from nltk.tokenize import word_tokenize
8
+ from nltk.corpus import stopwords
9
+ from nltk.stem import SnowballStemmer
10
+ import pandas as pd
11
+ from fpdf import FPDF
12
+ import streamlit as st
13
+ import requests
14
+ from google.cloud import texttospeech
15
+
16
+ nltk.download('punkt', quiet=True)
17
+ nltk.download('stopwords', quiet=True)
18
 
19
  # Cargar las claves API desde el archivo .env
20
  load_dotenv()
21
  openai_api_key = os.getenv("OPENAI_API_KEY")
22
+ brevo_api_key = os.getenv("BREVO_API_KEY")
23
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "botidinamix-g.json"
24
 
25
  # Verifica que las claves API están configuradas
26
  if not openai_api_key:
 
28
  else:
29
  openai.api_key = openai_api_key
30
 
31
+ if not brevo_api_key:
32
+ st.error("No API key provided for Brevo. Please set your API key in the .env file.")
33
+
34
+ def extraer_texto_pdf(archivo):
35
+ texto = ""
36
+ if archivo:
37
+ with tempfile.NamedTemporaryFile(delete=False) as temp_file:
38
+ temp_file.write(archivo.read())
39
+ temp_file_path = temp_file.name
40
+ try:
41
+ with open(temp_file_path, 'rb') as file:
42
+ reader = PyPDF2.PdfReader(file)
43
+ for page in range(len(reader.pages)):
44
+ texto += reader.pages[page].extract_text()
45
+ except Exception as e:
46
+ st.error(f"Error al extraer texto del PDF: {e}")
47
+ finally:
48
+ os.unlink(temp_file_path)
49
+ return texto
50
+
51
+ def preprocesar_texto(texto):
52
+ tokens = word_tokenize(texto, language='spanish')
53
+ tokens = [word.lower() for word in tokens if word.isalpha()]
54
+ stopwords_es = set(stopwords.words('spanish'))
55
+ tokens = [word for word in tokens if word not in stopwords_es]
56
+ stemmer = SnowballStemmer('spanish')
57
+ tokens = [stemmer.stem(word) for word in tokens]
58
+ return " ".join(tokens)
59
+
60
+ def obtener_respuesta(pregunta, texto_preprocesado, modelo, temperatura=0.5, assistant_id=""):
61
+ try:
62
+ response = openai.ChatCompletion.create(
63
+ model=modelo,
64
+ messages=[
65
+ {"role": "system", "content": "Actua como Galatea la asistente de la clinica Odontologica OMARDENT y resuelve las inquietudes"},
66
+ {"role": "user", "content": f"{pregunta}\n\nContexto: {texto_preprocesado}"}
67
+ ],
68
+ temperature=temperatura
69
+ )
70
+ respuesta = response.choices[0].message['content'].strip()
71
+
72
+ # Configura la solicitud de síntesis de voz
73
+ client = texttospeech.TextToSpeechClient()
74
+ input_text = texttospeech.SynthesisInput(text=respuesta)
75
+ voice = texttospeech.VoiceSelectionParams(
76
+ language_code="es-ES", ssml_gender=texttospeech.SsmlVoiceGender.FEMALE
77
+ )
78
+ audio_config = texttospeech.AudioConfig(
79
+ audio_encoding=texttospeech.AudioEncoding.MP3
80
+ )
81
+
82
+ # Realiza la solicitud de síntesis de voz
83
+ response = client.synthesize_speech(
84
+ input=input_text, voice=voice, audio_config=audio_config
85
+ )
86
+
87
+ # Reproduce el audio en Streamlit
88
+ st.audio(response.audio_content, format="audio/mp3")
89
+ return respuesta
90
+
91
+ except openai.OpenAIError as e:
92
+ st.error(f"Error al comunicarse con OpenAI: {e}")
93
+ return "Lo siento, no puedo procesar tu solicitud en este momento."
94
+
95
+ except Exception as e:
96
+ st.error(f"Error al generar la respuesta y el audio: {e}")
97
+ return "Lo siento, ocurrió un error al procesar tu solicitud."
98
+
99
+ def guardar_en_txt(nombre_archivo, datos):
100
+ carpeta = "datos_guardados"
101
+ os.makedirs(carpeta, existo_ok=True)
102
+ ruta_archivo = os.path.join(carpeta, nombre_archivo)
103
+ try:
104
+ with open(ruta_archivo, 'a', encoding='utf-8') as archivo: # Append mode
105
+ archivo.write(datos + "\n")
106
+ except Exception as e:
107
+ st.error(f"Error al guardar datos en el archivo: {e}")
108
+ return ruta_archivo
109
+
110
+ def cargar_desde_txt(nombre_archivo):
111
+ carpeta = "datos_guardados"
112
+ ruta_archivo = os.path.join(carpeta, nombre_archivo)
113
+ try:
114
+ if os.path.exists(ruta_archivo):
115
+ with open(ruta_archivo, 'r', encoding='utf-8') as archivo:
116
+ return archivo.read()
117
+ else:
118
+ st.warning("Archivo no encontrado.")
119
+ return ""
120
+ except Exception as e:
121
+ st.error(f"Error al cargar datos desde el archivo: {e}")
122
+ return ""
123
+
124
+ def listar_archivos_txt():
125
+ carpeta = "datos_guardados"
126
+ try:
127
+ if not os.path.exists(carpeta):
128
+ return []
129
+ archivos = [f for f in os.listdir(carpeta) if f.endswith('.txt')]
130
+ archivos_ordenados = sorted(archivos, key=lambda x: os.path.getctime(os.path.join(carpeta, x)), reverse=True)
131
+ return archivos_ordenados
132
+ except Exception as e:
133
+ st.error(f"Error al listar archivos: {e}")
134
+ return []
135
+
136
+ def generar_pdf(dataframe, titulo, filename):
137
+ pdf = FPDF()
138
+ pdf.add_page()
139
+ pdf.set_font("Arial", size=12)
140
+ pdf.cell(200, 10, txt=titulo, ln=True, align='C')
141
+
142
+ for i, row in dataframe.iterrows():
143
+ row_text = ", ".join(f"{col}: {val}" for col, val in row.items())
144
+ pdf.cell(200, 10, txt=row_text, ln=True)
145
+
146
+ try:
147
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
148
+ pdf.output(tmp_file.name)
149
+ return tmp_file.name
150
+ except Exception as e:
151
+ st.error(f"Error al generar PDF: {e}")
152
+ return None
153
+
154
+ def enviar_correo(destinatario, asunto, contenido):
155
+ url = "https://api.brevo.com/v3/smtp/email"
156
+ headers = {
157
+ "accept": "application/json",
158
+ "api-key": brevo_api_key,
159
+ "content-type": "application/json"
160
+ }
161
+ payload = {
162
+ "sender": {"email": "tu_correo@dominio.com"},
163
+ "to": [{"email": destinatario}],
164
+ "subject": asunto,
165
+ "htmlContent": contenido
166
+ }
167
+ try:
168
+ response = requests.post(url, json=payload, headers=headers)
169
+ if response.status_code == 201:
170
+ st.success(f"Correo enviado a {destinatario}")
171
+ else:
172
+ st.error(f"Error al enviar el correo: {response.text}")
173
+ except Exception as e:
174
+ st.error(f"Error al enviar el correo: {e}")
175
+
176
+ def enviar_whatsapp(numero, mensaje):
177
+ url = "https://api.brevo.com/v3/whatsapp/send"
178
+ headers = {
179
+ "accept": "application/json",
180
+ "api-key": brevo_api_key,
181
+ "content-type": "application/json"
182
+ }
183
+ payload = {
184
+ "recipient": {"number": numero},
185
+ "sender": {"number": "tu_numero_whatsapp"},
186
+ "content": mensaje
187
+ }
188
+ try:
189
+ response = requests.post(url, json=payload, headers=headers)
190
+ if response.status_code == 201:
191
+ st.success(f"Mensaje de WhatsApp enviado a {numero}")
192
+ else:
193
+ st.error(f"Error al enviar el mensaje de WhatsApp: {response.text}")
194
+ except Exception as e:
195
+ st.error(f"Error al enviar el mensaje de WhatsApp: {e}")
196
+
197
+ def flujo_laboratorio():
198
+ st.title("🦷 Gestión de Trabajos de Laboratorio")
199
+
200
+ if 'laboratorio' not in st.session_state:
201
+ st.session_state.laboratorio = []
202
+
203
+ with st.form("laboratorio_form"):
204
+ tipo_trabajo = st.selectbox("Tipo de trabajo:", [
205
+ "Protesis total", "Protesis removible metal-acrilico", "Parcialita acrilico",
206
+ "Placa de blanqueamiento", "Placa de bruxismo", "Corona de acrilico",
207
+ "Corona en zirconio", "Protesis flexible", "Acker flexible"
208
+ ])
209
+ doctor = st.selectbox("Doctor que requiere el trabajo:", ["Dr. Jose Daniel C", "Dr. Jose Omar C"])
210
+ fecha_entrega = st.date_input("Fecha de entrega:")
211
+ fecha_envio = st.date_input("Fecha de envío:")
212
+ laboratorio = st.selectbox("Laboratorio dental:", ["Ernesto Correa lab", "Formando Sonrisas"])
213
+ nombre_paciente = st.text_input("Nombre paciente:")
214
+ observaciones = st.text_input("Observaciones:")
215
+ numero_orden = st.text_input("Número de orden:")
216
+ cantidad = st.number_input("Cantidad:", min_value=1, step=1)
217
+
218
+ submitted = st.form_submit_button("Registrar Trabajo")
219
+
220
+ if submitted:
221
+ trabajo = {
222
+ "tipo_trabajo": tipo_trabajo,
223
+ "doctor": doctor,
224
+ "fecha_entrega": str(fecha_entrega),
225
+ "fecha_envio": str(fecha_envio),
226
+ "laboratorio": laboratorio,
227
+ "nombre_paciente": nombre_paciente,
228
+ "observaciones": observaciones,
229
+ "numero_orden": numero_orden,
230
+ "cantidad": cantidad,
231
+ "estado": "pendiente"
232
+ }
233
+ st.session_state.laboratorio.append(trabajo)
234
+ datos_guardados = mostrar_datos_como_texto([trabajo]) # Append only the new entry
235
+ guardar_en_txt('trabajos_laboratorio.txt', datos_guardados)
236
+ st.success("Trabajo registrado con éxito.")
237
+
238
+ if st.session_state.laboratorio:
239
+ st.write("### Trabajos Registrados")
240
+ df_trabajos = pd.DataFrame(st.session_state.laboratorio)
241
+ st.write(df_trabajos)
242
+
243
+ pdf_file = generar_pdf(df_trabajos, "Registro de Trabajos de Laboratorio", "trabajos_laboratorio.pdf")
244
+ st.download_button(
245
+ label="📥 Descargar PDF",
246
+ data=open(pdf_file, 'rb').read(),
247
+ file_name="trabajos_laboratorio.pdf",
248
+ mime="application/pdf"
249
+ )
250
+
251
+ def flujo_insumos():
252
+ st.title("📦 Gestión de Insumos")
253
+
254
+ if 'insumos' not in st.session_state:
255
+ st.session_state.insumos = []
256
+
257
+ with st.form("insumos_form"):
258
+ insumo_nombre = st.text_input("Nombre del Insumo:")
259
+ insumo_cantidad = st.number_input("Cantidad Faltante:", min_value=0, step=1)
260
+ submitted = st.form_submit_button("Agregar Insumo")
261
+
262
+ if submitted and insumo_nombre:
263
+ insumo = {"nombre": insumo_nombre, "cantidad": insumo_cantidad}
264
+ st.session_state.insumos.append(insumo)
265
+ datos_guardados = mostrar_datos_como_texto([insumo]) # Append only the new entry
266
+ guardar_en_txt('insumos.txt', datos_guardados)
267
+ st.success(f"Insumo '{insumo_nombre}' agregado con éxito.")
268
+
269
+ if st.session_state.insumos:
270
+ st.write("### Insumos Registrados")
271
+ insumos_df = pd.DataFrame(st.session_state.insumos)
272
+ st.write(insumos_df)
273
+
274
+ pdf_file = generar_pdf(insumos_df, "Registro de Insumos Faltantes", "insumos.pdf")
275
+ st.download_button(
276
+ label="📥 Descargar PDF",
277
+ data=open(pdf_file, 'rb').read(),
278
+ file_name="insumos_faltantes.pdf",
279
+ mime="application/pdf"
280
+ )
281
+
282
+ def buscar_datos_guardados():
283
+ st.title("🔍 Buscar Datos Guardados")
284
+
285
+ carpeta = "datos_guardados"
286
+ if not os.path.exists(carpeta):
287
+ st.info("No se encontraron archivos de datos guardados.")
288
+ return
289
+
290
+ archivos = listar_archivos_txt()
291
+
292
+ if archivos:
293
+ archivo_seleccionado = st.selectbox("Selecciona un archivo para ver:", archivos)
294
+
295
+ if archivo_seleccionado:
296
+ datos = cargar_desde_txt(os.path.join(carpeta, archivo_seleccionado))
297
+ if datos:
298
+ st.write(f"### Datos del archivo {archivo_seleccionado}")
299
+ st.text_area("Datos", datos, height=300)
300
+
301
+ # Link to download the file
302
+ try:
303
+ with open(os.path.join(carpeta, archivo_seleccionado), 'rb') as file:
304
+ st.download_button(
305
+ label="📥 Descargar Archivo TXT",
306
+ data=file,
307
+ file_name=archivo_seleccionado,
308
+ mime="text/plain"
309
+ )
310
+ except Exception as e:
311
+ st.error(f"Error al preparar la descarga: {e}")
312
+
313
+ # Enviar el archivo seleccionado por correo
314
+ if st.button("Enviar por correo"):
315
+ contenido = f"Datos del archivo {archivo_seleccionado}:\n\n{datos}"
316
+ enviar_correo("josedcape@gmail.com", f"Datos del archivo {archivo_seleccionado}", contenido)
317
+
318
+ # Enviar el archivo seleccionado por WhatsApp
319
+ if st.button("Enviar por WhatsApp"):
320
+ mensaje = f"Datos del archivo {archivo_seleccionado}:\n\n{datos}"
321
+ enviar_whatsapp("3114329322", mensaje)
322
+
323
+ else:
324
+ st.warning(f"No se encontraron datos en el archivo {archivo_seleccionado}")
325
+ else:
326
+ st.info("No se encontraron archivos de datos guardados.")
327
 
328
+ def generar_notificaciones_pendientes():
329
+ if 'laboratorio' not in st.session_state or not st.session_state.laboratorio:
330
+ st.info("No hay trabajos pendientes.")
331
+ return
332
+
333
+ pendientes = [trabajo for trabajo in st.session_state.laboratorio if trabajo["estado"] == "pendiente"]
334
+ if pendientes:
335
+ st.write("### Notificaciones de Trabajos Pendientes")
336
+ for trabajo in pendientes:
337
+ st.info(f"Pendiente: {trabajo['tipo_trabajo']} - {trabajo['numero_orden']} para {trabajo['doctor']}. Enviado a {trabajo['laboratorio']} el {trabajo['fecha_envio']}.")
338
+
339
+ def mostrar_datos_como_texto(datos):
340
+ texto = ""
341
+ if isinstance(datos, dict):
342
+ for key, value in datos.items():
343
+ texto += f"{key}: {value}\n"
344
+ elif isinstance(datos, list):
345
+ for item in datos:
346
+ if isinstance(item, dict):
347
+ for key, value in item.items():
348
+ texto += f"{key}: {value}\n"
349
+ texto += "\n"
350
+ else:
351
+ texto += f"{item}\n"
352
+ return texto
353
+
354
+ def flujo_presupuestos():
355
+ st.title("💰 Asistente de Presupuestos")
356
+ st.markdown("Hola Dr. cuénteme en que puedo ayudarle?")
357
+
358
+ lista_precios = {
359
+ "Restauraciones en resina de una superficie": 75000,
360
+ "Restauraciones en resina de dos superficies": 95000,
361
+ "Restauraciones en resina de tres o más superficies": 120000,
362
+ "Restauración en resina cervical": 60000,
363
+ "Coronas metal-porcelana": 750000,
364
+ "Provisional": 80000,
365
+ "Profilaxis simple": 75000,
366
+ "Profilaxis completa": 90000,
367
+ "Corona en zirconio": 980000,
368
+ "Blanqueamiento dental láser por sesión": 150000,
369
+ "Blanqueamiento dental casero": 330000,
370
+ "Blanqueamiento mixto": 430000,
371
+ "Prótesis parcial acrílico hasta 6 dientes": 530000,
372
+ "Prótesis parcial acrílico de más de 6 dientes": 580000,
373
+ "Prótesis flexible hasta 6 dientes": 800000,
374
+ "Prótesis flexible de más de 6 dientes": 900000,
375
+ "Prótesis total de alto impacto": 650000,
376
+ "Acker flexible hasta 2 dientes": 480000,
377
+ "Exodoncia por diente": 85000,
378
+ "Exodoncia cordal": 130000,
379
+ "Endodoncia con dientes terminados en 6": 580000,
380
+ "Endodoncia de un conducto": 380000,
381
+ "Endodoncia de premolares superiores": 480000,
382
+ }
383
+
384
+ if 'presupuesto' not in st.session_state:
385
+ st.session_state['presupuesto'] = []
386
+
387
+ with st.form("presupuesto_form"):
388
+ tratamiento = st.selectbox("Selecciona el tratamiento", list(lista_precios.keys()))
389
+ cantidad = st.number_input("Cantidad", min_value=1, step=1)
390
+ agregar = st.form_submit_button("Agregar al Presupuesto")
391
+
392
+ if agregar:
393
+ precio_total = lista_precios[tratamiento] * cantidad
394
+ st.session_state['presupuesto'].append({"tratamiento": tratamiento, "cantidad": cantidad, "precio_total": precio_total})
395
+ st.success(f"Agregado: {cantidad} {tratamiento} - Total: {precio_total} COP")
396
+
397
+ if st.session_state['presupuesto']:
398
+ st.write("### Servicios Seleccionados")
399
+ total_presupuesto = sum(item['precio_total'] for item in st.session_state['presupuesto'])
400
+ for item in st.session_state['presupuesto']:
401
+ st.write(f"{item['cantidad']} x {item['tratamiento']} - {item['precio_total']} COP")
402
+ st.write(f"**Total: {total_presupuesto} COP**")
403
+
404
+ if st.button("Copiar Presupuesto al Asistente"):
405
+ servicios = "\n".join([f"{item['cantidad']} x {item['tratamiento']} - {item['precio_total']} COP" for item in st.session_state['presupuesto']])
406
+ total = f"**Total: {total_presupuesto} COP**"
407
+ st.session_state['presupuesto_texto'] = f"{servicios}\n{total}"
408
+ st.success("Presupuesto copiado al asistente de chat.")
409
+ st.session_state['mostrar_chat'] = True
410
+
411
+ if st.session_state['mostrar_chat']:
412
+ st.markdown("### Chat con Asistente")
413
+ pregunta_usuario = st.text_input("Escribe tu pregunta aquí:", value=st.session_state.get('presupuesto_texto', ''))
414
+ if st.button("Enviar Pregunta"):
415
+ manejar_pregunta_usuario(pregunta_usuario)
416
+
417
+ def flujo_radiografias():
418
+ st.title("📸 Registro de Radiografías")
419
+
420
+ if 'radiografias' not in st.session_state:
421
+ st.session_state.radiografias = []
422
+
423
+ with st.form("radiografias_form"):
424
+ nombre_paciente = st.text_input("Nombre del Paciente:")
425
+ tipo_radiografia = st.selectbox("Tipo de Radiografía:", ["Periapical", "Panorámica", "Cefalométrica"])
426
+ fecha_realizacion = st.date_input("Fecha de Realización:")
427
+ observaciones = st.text_area("Observaciones:")
428
+
429
+ submitted = st.form_submit_button("Registrar Radiografía")
430
+
431
+ if submitted:
432
+ radiografia = {
433
+ "nombre_paciente": nombre_paciente,
434
+ "tipo_radiografia": tipo_radiografia,
435
+ "fecha_realizacion": str(fecha_realizacion),
436
+ "observaciones": observaciones
437
+ }
438
+ st.session_state.radiografias.append(radiografia)
439
+ datos_guardados = mostrar_datos_como_texto([radiografia])
440
+ guardar_en_txt('radiografias.txt', datos_guardados)
441
+ st.success("Radiografía registrada con éxito.")
442
+
443
+ if st.session_state.radiografias:
444
+ st.write("### Radiografías Registradas")
445
+ df_radiografias = pd.DataFrame(st.session_state.radiografias)
446
+ st.write(df_radiografias)
447
+
448
+ pdf_file = generar_pdf(df_radiografias, "Registro de Radiografías", "radiografias.pdf")
449
+ st.download_button(
450
+ label="📥 Descargar PDF",
451
+ data=open(pdf_file, 'rb').read(),
452
+ file_name="radiografias.pdf",
453
+ mime="application/pdf"
454
+ )
455
+
456
+ def mostrar_recomendaciones():
457
+ st.title("⭐ Recomendaciones")
458
+ st.write("Aquí puedes encontrar recomendaciones y consejos útiles.")
459
 
460
+ def main():
461
+ st.set_page_config(page_title="Galatea OMARDENT", layout="wide")
462
+
463
+ # Inicializar el estado de la sesión
464
  if 'modelo' not in st.session_state:
465
+ st.session_state['modelo'] = "gpt-3.5-turbo"
466
  if 'temperatura' not in st.session_state:
467
  st.session_state['temperatura'] = 0.5
468
  if 'mensajes_chat' not in st.session_state:
 
474
  if 'video_estado' not in st.session_state:
475
  st.session_state['video_estado'] = 'paused'
476
  if 'assistant_id' not in st.session_state:
477
+ st.session_state['assistant_id'] = 'asst_4ZYvBvf4IUVQPjnugSZGLdV2'
478
+ if 'presupuesto_texto' not in st.session_state:
479
+ st.session_state['presupuesto_texto'] = ''
480
+ if 'mostrar_chat' not in st.session_state:
481
+ st.session_state['mostrar_chat'] = False
482
 
483
+ # Barra lateral
 
 
484
  ruta_logo = os.path.join("assets", "Logo Omardent.png")
485
  if os.path.exists(ruta_logo):
486
  st.sidebar.image(ruta_logo, use_column_width=True)
 
505
  step=0.1,
506
  key='temperatura_slider' # Clave única
507
  )
508
+ assistant_id = st.sidebar.text_input("Assistant ID", key="assistant_id", help="Introduce el Assistant ID del playground de OpenAI")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
 
510
+ st.sidebar.markdown("---")
511
+ st.sidebar.subheader("🌟 Navegación")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
  lateral_page = st.sidebar.radio("Ir a", ["Página Principal", "Gestión de Trabajos", "Gestión de Insumos", "Registro de Radiografías", "Buscar Datos", "Notificaciones", "Recomendaciones", "Asistente de Presupuestos", "Comunicación", "Asistente de Agendamiento"])
513
 
514
  top_page = st.selectbox("Navegación Superior", ["Página Principal", "Galatea-Asistente"])
 
529
  elif lateral_page == "Notificaciones":
530
  generar_notificaciones_pendientes()
531
  elif lateral_page == "Recomendaciones":
532
+ mostrar_recomendaciones()
533
  elif lateral_page == "Asistente de Presupuestos":
534
  flujo_presupuestos()
535
  elif lateral_page == "Comunicación":
 
620
  }
621
  </style>
622
  <div id="video-container">
623
+ <video id="background-video" autoplay loop muted playsinline>
624
+ <source src="https://cdn.leonardo.ai/users/645c3d5c-ca1b-4ce8-aefa-a091494e0d09/generations/aaa569a1-8952-4e8e-9c3c-71cc66e62f04/aaa569a1-8952-4e8e-9c3c-71cc66e62f04.mp4" type="video/mp4">
625
  </video>
626
  </div>
627
  <div id="chat-container">
 
644
  else:
645
  st.warning("No se ha cargado ninguna imagen. Por favor, carga una imagen en la página principal.")
646
 
647
+ def manejar_pregunta_usuario(pregunta_usuario, archivo_pdf=None):
648
+ st.session_state['mensajes_chat'].append({"role": "user", "content": pregunta_usuario})
649
+ with st.chat_message("user"):
650
+ st.markdown(pregunta_usuario)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
+ texto_preprocesado = ""
653
+ if archivo_pdf:
654
+ texto_pdf = extraer_texto_pdf(archivo_pdf)
655
+ texto_preprocesado = preprocesar_texto(texto_pdf)
656
 
657
+ # Obtener respuesta del modelo usando Assistant ID si está presente
658
+ assistant_id = st.session_state.get('assistant_id', '')
659
+ if assistant_id:
660
+ prompt = f"{texto_preprocesado}\n\n{pregunta_usuario}"
661
+ response = openai.ChatCompletion.create(
662
+ model=st.session_state['modelo'],
663
+ messages=[
664
+ {"role": "system", "content": "You are a helpful assistant."},
665
+ {"role": "user", "content": prompt}
666
+ ],
667
+ temperature=st.session_state['temperatura'],
668
+ user=assistant_id
669
+ )
670
+ respuesta = response.choices[0].message['content'].strip()
671
+ else:
672
+ respuesta = obtener_respuesta(
673
+ pregunta_usuario,
674
+ texto_preprocesado,
675
+ st.session_state['modelo'],
676
+ st.session_state['temperatura'],
677
+ assistant_id
678
+ )
679
+
680
+ st.session_state['mensajes_chat'].append({"role": "assistant", "content": respuesta})
681
+ with st.chat_message("assistant"):
682
+ st.markdown(respuesta)
683
 
684
+ # Convertir la respuesta en voz
685
+ client = texttospeech.TextToSpeechClient()
686
+ synthesis_input = texttospeech.SynthesisInput(text=respuesta)
687
+ voice = texttospeech.VoiceSelectionParams(language_code="es-ES", ssml_gender=texttospeech.SsmlVoiceGender.FEMALE)
688
+ audio_config = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.MP3)
689
+ response = client.synthesize_speech(input=synthesis_input, voice=voice, audio_config=audio_config)
690
+
691
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
692
+ tmp_file.write(response.audio_content)
693
+ audio_file_path = tmp_file.name
694
+
695
+ # Incrustar el audio en la página y reproducirlo automáticamente
696
+ audio_html = f"""
697
+ <audio id="response-audio" src="data:audio/mp3;base64,{base64.b64encode(response.audio_content).decode()}" autoplay></audio>
698
+ <script>
699
+ document.getElementById('response-audio').onended = function() {{
700
+ document.getElementById('background-video').pause();
701
+ }};
702
+ </script>
703
+ """
704
+ st.markdown(audio_html, unsafe_allow_html=True)
705
 
706
+ # Reproducir el video solo cuando el chat está activo
707
+ st.session_state['video_estado'] = 'playing'
708
+ st.markdown(f"<script>document.getElementById('background-video').play();</script>", unsafe_allow_html=True)
709
 
710
+ def capturar_voz():
711
+ st.markdown(
712
+ """
713
+ <style>
714
+ .assistant-button {
715
+ display: flex;
716
+ align-items: center;
717
+ justify-content: center;
718
+ background-color: #4CAF50;
719
+ color: white;
720
+ padding: 10px;
721
+ border: none;
722
+ border-radius: 5px;
723
+ cursor: pointer;
724
+ font-size: 16px;
725
+ margin-top: 10px;
726
+ }
727
+ .assistant-button img {
728
+ margin-right: 10px;
729
+ }
730
+ </style>
731
+ <button class="assistant-button" onclick="startRecording()">
732
+ <img src='https://img2.gratispng.com/20180808/cxq/kisspng-robotics-science-computer-icons-robot-technology-robo-to-logo-svg-png-icon-free-download-45527-5b6baa46a5e322.4713113715337825986795.jpg' alt='icon' width='20' height='20'/>
733
+ Capturar Voz
734
+ </button>
735
+ <script>
736
+ function startRecording() {
737
+ const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
738
+ recognition.lang = 'es-ES';
739
+ recognition.interimResults = false;
740
+ recognition.maxAlternatives = 1;
741
 
742
+ recognition.start();
 
 
 
 
743
 
744
+ recognition.onresult = (event) => {
745
+ const lastResult = event.results.length - 1;
746
+ const text = event.results[lastResult][0].transcript;
747
+ const customEvent = new CustomEvent('audioTranscription', { detail: text });
748
+ document.dispatchEvent(customEvent);
749
+ };
750
 
751
+ recognition.onspeechend = () => {
752
+ recognition.stop();
753
+ };
754
 
755
+ recognition.onerror = (event) => {
756
+ console.error(event.error);
757
+ };
758
+ }
759
+
760
+ document.addEventListener('audioTranscription', (event) => {
761
+ const transcription = event.detail;
762
+ document.querySelector("input[name='unique_chat_input_key']").value = transcription;
763
+ // También puedes actualizar el estado de Streamlit aquí si es necesario
764
+ fetch('/process_audio', {
765
+ method: 'POST',
766
+ headers: {
767
+ 'Content-Type': 'application/json'
768
+ },
769
+ body: JSON.stringify({ transcription })
770
+ }).then(response => response.json())
771
+ .then(data => {
772
+ // Manejo de la respuesta de Flask si es necesario
773
+ });
774
+ });
775
+ </script>
776
+ """,
777
+ unsafe_allow_html=True
778
+ )
779
 
780
  if __name__ == "__main__":
781
  main()