Josedcape commited on
Commit
fa9ba9d
·
verified ·
1 Parent(s): 0c05b3c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +593 -202
app.py CHANGED
@@ -1,30 +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
- flujo_radiografias
15
- )
16
  import tempfile
 
17
  from dotenv import load_dotenv
18
- from flask import Flask, request, jsonify
19
- import threading
20
- from google.oauth2.service_account import Credentials
21
- from googleapiclient.discovery import build
22
- from datetime import datetime, timedelta
 
 
 
 
 
 
 
 
23
 
24
  # Cargar las claves API desde el archivo .env
25
  load_dotenv()
26
  openai_api_key = os.getenv("OPENAI_API_KEY")
27
- google_credentials = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
 
28
 
29
  # Verifica que las claves API están configuradas
30
  if not openai_api_key:
@@ -32,29 +28,441 @@ if not openai_api_key:
32
  else:
33
  openai.api_key = openai_api_key
34
 
35
- if not google_credentials:
36
- st.error("No Google credentials provided. Please set your credentials in the .env file.")
37
-
38
- # Crear la aplicación Flask
39
- app = Flask(__name__)
40
-
41
- @app.route('/process_audio', methods=['POST'])
42
- def process_audio():
43
- data = request.json
44
- transcription = data['transcription']
45
- return jsonify({'transcription': transcription})
46
-
47
- def run_flask_app():
48
- app.run(port=5000, debug=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- # Ejecutar Flask en un hilo separado
51
- flask_thread = threading.Thread(target=run_flask_app)
52
- flask_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
- def inicializar_estado():
55
- """Inicializa el estado de la sesión si no está ya inicializado."""
 
 
56
  if 'modelo' not in st.session_state:
57
- st.session_state['modelo'] = "gpt-3.5-turbo-16k"
58
  if 'temperatura' not in st.session_state:
59
  st.session_state['temperatura'] = 0.5
60
  if 'mensajes_chat' not in st.session_state:
@@ -65,10 +473,14 @@ def inicializar_estado():
65
  st.session_state['imagen_asistente'] = None
66
  if 'video_estado' not in st.session_state:
67
  st.session_state['video_estado'] = 'paused'
68
-
69
- inicializar_estado()
70
-
71
- def barra_lateral():
 
 
 
 
72
  ruta_logo = os.path.join("assets", "Logo Omardent.png")
73
  if os.path.exists(ruta_logo):
74
  st.sidebar.image(ruta_logo, use_column_width=True)
@@ -94,153 +506,9 @@ def barra_lateral():
94
  key='temperatura_slider' # Clave única
95
  )
96
  assistant_id = st.sidebar.text_input("Assistant ID", key="assistant_id", help="Introduce el Assistant ID del playground de OpenAI")
97
- if assistant_id:
98
- st.session_state['assistant_id'] = assistant_id
99
-
100
- def mostrar_mensajes_chat():
101
- for mensaje in st.session_state['mensajes_chat']:
102
- with st.chat_message(mensaje["role"]):
103
- st.markdown(mensaje["content"])
104
-
105
- def manejar_pregunta_usuario(pregunta_usuario, archivo_pdf=None):
106
- st.session_state['mensajes_chat'].append({"role": "user", "content": pregunta_usuario})
107
- with st.chat_message("user"):
108
- st.markdown(pregunta_usuario)
109
-
110
- texto_preprocesado = ""
111
- if archivo_pdf:
112
- texto_pdf = extraer_texto_pdf(archivo_pdf)
113
- texto_preprocesado = preprocesar_texto(texto_pdf)
114
-
115
- respuesta = obtener_respuesta(
116
- pregunta_usuario,
117
- texto_preprocesado,
118
- st.session_state['modelo'],
119
- st.session_state['temperatura'],
120
- st.session_state.get('assistant_id', '')
121
- )
122
 
123
- # Consulta de Google Calendar
124
- if "cita" in pregunta_usuario.lower():
125
- respuesta += "\n\n" + consultar_google_calendar(pregunta_usuario)
126
-
127
- st.session_state['mensajes_chat'].append({"role": "assistant", "content": respuesta})
128
- with st.chat_message("assistant"):
129
- st.markdown(respuesta)
130
-
131
- # Convertir la respuesta en voz
132
- client = texttospeech.TextToSpeechClient()
133
- synthesis_input = texttospeech.SynthesisInput(text=respuesta)
134
- voice = texttospeech.VoiceSelectionParams(language_code="es-ES", ssml_gender=texttospeech.SsmlVoiceGender.NEUTRAL)
135
- audio_config = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.MP3)
136
- response = client.synthesize_speech(input=synthesis_input, voice=voice, audio_config=audio_config)
137
-
138
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
139
- tmp_file.write(response.audio_content)
140
- st.audio(tmp_file.name)
141
-
142
- # Reproducir el video solo cuando el chat está activo
143
- st.session_state['video_estado'] = 'playing'
144
-
145
- def capturar_voz():
146
- st.markdown(
147
- """
148
- <style>
149
- .assistant-button {
150
- display: flex;
151
- align-items: center;
152
- justify-content: center;
153
- background-color: #4CAF50;
154
- color: white;
155
- padding: 10px;
156
- border: none;
157
- border-radius: 5px;
158
- cursor: pointer;
159
- font-size: 16px;
160
- margin-top: 10px;
161
- }
162
- .assistant-button img {
163
- margin-right: 10px;
164
- }
165
- </style>
166
- <button class="assistant-button" onclick="startRecording()">
167
- <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'/>
168
- Capturar Voz
169
- </button>
170
- <script>
171
- function startRecording() {
172
- const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
173
- recognition.lang = 'es-ES';
174
- recognition.interimResults = false;
175
- recognition.maxAlternatives = 1;
176
-
177
- recognition.start();
178
-
179
- recognition.onresult = (event) => {
180
- const lastResult = event.results.length - 1;
181
- const text = event.results[lastResult][0].transcript;
182
- const customEvent = new CustomEvent('audioTranscription', { detail: text });
183
- document.dispatchEvent(customEvent);
184
- };
185
-
186
- recognition.onspeechend = () => {
187
- recognition.stop();
188
- };
189
-
190
- recognition.onerror = (event) => {
191
- console.error(event.error);
192
- };
193
- }
194
-
195
- document.addEventListener('audioTranscription', (event) => {
196
- const transcription = event.detail;
197
- document.querySelector("input[name='unique_chat_input_key']").value = transcription;
198
- // También puedes actualizar el estado de Streamlit aquí si es necesario
199
- fetch('/process_audio', {
200
- method: 'POST',
201
- headers: {
202
- 'Content-Type': 'application/json'
203
- },
204
- body: JSON.stringify({ transcription })
205
- }).then(response => response.json())
206
- .then(data => {
207
- // Manejo de la respuesta de Flask si es necesario
208
- });
209
- });
210
- </script>
211
- """,
212
- unsafe_allow_html=True
213
- )
214
-
215
- def consultar_google_calendar(pregunta):
216
- SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
217
- creds = None
218
-
219
- if not os.path.exists('service_account.json'):
220
- return 'Error: archivo service_account.json no encontrado.'
221
-
222
- creds = Credentials.from_service_account_file('service_account.json', scopes=SCOPES)
223
-
224
- service = build('calendar', 'v3', credentials=creds)
225
-
226
- now = datetime.utcnow().isoformat() + 'Z'
227
- events_result = service.events().list(
228
- calendarId='primary', timeMin=now,
229
- maxResults=10, singleEvents=True,
230
- orderBy='startTime').execute()
231
- events = events_result.get('items', [])
232
-
233
- if not events:
234
- return 'No hay próximas citas encontradas.'
235
- else:
236
- eventos = []
237
- for event in events:
238
- start = event['start'].get('dateTime', event['start'].get('date'))
239
- eventos.append(f"{event['summary']} at {start}")
240
- return '\n'.join(eventos)
241
-
242
- def mostrar_paginas():
243
- st.sidebar.title("Navegación Lateral")
244
  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"])
245
 
246
  top_page = st.selectbox("Navegación Superior", ["Página Principal", "Galatea-Asistente"])
@@ -261,7 +529,7 @@ def mostrar_paginas():
261
  elif lateral_page == "Notificaciones":
262
  generar_notificaciones_pendientes()
263
  elif lateral_page == "Recomendaciones":
264
- st.write("Página de Recomendaciones") # Implementar según sea necesario
265
  elif lateral_page == "Asistente de Presupuestos":
266
  flujo_presupuestos()
267
  elif lateral_page == "Comunicación":
@@ -352,8 +620,8 @@ def mostrar_galatea_asistente():
352
  }
353
  </style>
354
  <div id="video-container">
355
- <video id="background-video" {st.session_state['video_estado']} loop muted>
356
- <source src="./Welcome to.mp4" type="video/mp4">
357
  </video>
358
  </div>
359
  <div id="chat-container">
@@ -376,15 +644,138 @@ def mostrar_galatea_asistente():
376
  else:
377
  st.warning("No se ha cargado ninguna imagen. Por favor, carga una imagen en la página principal.")
378
 
379
- def main():
380
- inicializar_estado()
381
- barra_lateral()
382
- mostrar_paginas()
383
-
384
- if st.session_state['imagen_asistente'] is None:
385
- imagen_asistente = st.file_uploader("Cargar Imagen del Asistente", type=['jpg', 'jpeg', 'png'], key='imagen_asistente')
386
- if imagen_asistente:
387
- st.session_state['imagen_asistente'] = imagen_asistente
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
  if __name__ == "__main__":
390
  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:
 
473
  st.session_state['imagen_asistente'] = None
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)
 
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()