JeCabrera commited on
Commit
cecc3cf
·
verified ·
1 Parent(s): 10b679c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -64
app.py CHANGED
@@ -2,14 +2,19 @@ import time
2
  import os
3
  import joblib
4
  import streamlit as st
5
- import google.generativeai as genai
6
  from dotenv import load_dotenv
7
- from reel_formulas import reel_formulas # Cambiar de puv_formulas a reel_formulas
8
  from system_prompts import get_unified_reel_prompt # Cambiar de get_unified_puv_prompt a get_unified_reel_prompt
9
- from session_state import SessionState
 
 
 
 
 
 
10
 
11
  # Inicializar el estado de la sesión
12
  state = SessionState()
 
13
 
14
  # Función para detectar saludos y generar respuestas personalizadas
15
  def is_greeting(text):
@@ -43,11 +48,14 @@ def process_message(prompt, is_example=False):
43
  typing_indicator.markdown("*Generando respuesta...*")
44
 
45
  response = state.send_message(enhanced_prompt)
46
- full_response = stream_response(response, message_placeholder, typing_indicator)
47
 
48
  if full_response:
49
  state.add_message(MODEL_ROLE, full_response, AI_AVATAR_ICON)
50
- state.gemini_history = state.chat.history
 
 
 
51
  state.save_chat_history()
52
 
53
  except Exception as e:
@@ -63,50 +71,96 @@ def handle_chat_title(prompt):
63
  past_chats[state.chat_id] = state.chat_title
64
  else:
65
  state.chat_title = past_chats[state.chat_id]
66
- joblib.dump(past_chats, 'data/past_chats_list')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  def get_enhanced_prompt(prompt, is_example):
69
  """Genera el prompt mejorado según el tipo de mensaje"""
 
 
 
 
 
70
  if is_greeting(prompt):
71
  return f"El usuario te ha saludado con '{prompt}'. Preséntate brevemente, explica qué es un Reel y por qué es importante, y haz las 3 preguntas iniciales para comenzar a crear el guion del Reel (audiencia ideal, producto/servicio, y llamado a la acción). Sé amigable, breve y toma la iniciativa como el experto que eres."
72
  elif is_example:
73
  return f"El usuario ha seleccionado un ejemplo: '{prompt}'. Responde de manera conversacional y sencilla, como si estuvieras hablando con un amigo. Evita tecnicismos innecesarios. Enfócate en dar información práctica que ayude al usuario a crear su Reel. Usa ejemplos concretos cuando sea posible. Termina tu respuesta con una pregunta que invite al usuario a compartir información sobre su negocio para poder ayudarle a crear su Reel personalizado."
74
  return prompt
75
 
76
- def process_model_response(enhanced_prompt):
77
- """Procesa la respuesta del modelo"""
78
- with st.chat_message(MODEL_ROLE, avatar=AI_AVATAR_ICON):
79
- try:
80
- message_placeholder = st.empty()
81
- typing_indicator = st.empty()
82
- typing_indicator.markdown("*Generando respuesta...*")
83
-
84
- response = state.send_message(enhanced_prompt)
85
- full_response = stream_response(response, message_placeholder, typing_indicator)
86
-
87
- # Actualizar historial
88
- state.add_message(role=MODEL_ROLE, content=full_response, avatar=AI_AVATAR_ICON)
89
- state.gemini_history = state.chat.history
90
- state.save_chat_history()
91
-
92
- except Exception as e:
93
- st.error(f"Error: {str(e)}")
94
-
95
- def stream_response(response, message_placeholder, typing_indicator):
96
  """Maneja el streaming de la respuesta"""
97
  full_response = ''
 
 
 
 
98
  try:
99
  for chunk in response:
100
  if chunk.text:
101
  for ch in chunk.text:
102
  full_response += ch
103
- time.sleep(0.01)
104
- typing_indicator.markdown("*Generando respuesta...*")
105
- message_placeholder.markdown(full_response + '▌')
 
 
 
106
  except Exception as e:
107
  st.error(f"Error en el streaming: {str(e)}")
108
  return ''
109
-
 
 
 
 
 
110
  typing_indicator.empty()
111
  message_placeholder.markdown(full_response)
112
  return full_response
@@ -184,17 +238,21 @@ def display_examples():
184
  ]
185
 
186
  # Crear los botones de ejemplo
 
187
  cols = st.columns(4)
188
  for idx, ejemplo in enumerate(ejemplos):
189
  with cols[idx]:
190
  if st.button(ejemplo["texto"], key=f"ejemplo_{idx}", help=ejemplo["prompt"]):
191
- state.prompt = ejemplo["prompt"]
 
192
  st.rerun()
193
 
194
  # Cargar variables de entorno
195
  load_dotenv()
196
- GOOGLE_API_KEY=os.environ.get('GOOGLE_API_KEY')
197
- genai.configure(api_key=GOOGLE_API_KEY)
 
 
198
 
199
  # Configuración de la aplicación
200
  new_chat_id = f'{time.time()}'
@@ -202,22 +260,33 @@ MODEL_ROLE = 'ai'
202
  AI_AVATAR_ICON = '🤖' # Cambia el emoji por uno de robot para coincidir con tu logo
203
  USER_AVATAR_ICON = '👤' # Añade un avatar para el usuario
204
 
 
 
 
 
 
 
 
 
 
 
 
205
  # Crear carpeta de datos si no existe
206
  try:
207
- os.mkdir('data/')
208
- except:
209
- # data/ folder already exists
210
  pass
211
 
212
  # Cargar chats anteriores
213
  try:
214
- past_chats: dict = joblib.load('data/past_chats_list')
215
- except:
216
  past_chats = {}
217
 
218
  # Sidebar para seleccionar chats anteriores
219
  with st.sidebar:
220
  st.write('# Chats Anteriores')
 
221
  if state.chat_id is None:
222
  state.chat_id = st.selectbox(
223
  label='Selecciona un chat anterior',
@@ -241,8 +310,9 @@ with st.sidebar:
241
  state.load_chat_history()
242
 
243
  # Inicializar el modelo y el chat
244
- state.initialize_model('gemini-3.1-flash-lite-preview')
245
- state.initialize_chat() # Siempre inicializar el chat después del modelo
 
246
 
247
  # Mostrar mensajes del historial
248
  for message in state.messages:
@@ -252,28 +322,38 @@ for message in state.messages:
252
  ):
253
  st.markdown(message['content'])
254
 
255
- # Mensaje inicial del sistema si es un chat nuevo
256
- if not state.has_messages():
257
- # Mostrar la carátula inicial con el logo centrado
258
- display_initial_header()
259
-
260
- # Mostrar los ejemplos
261
- display_examples()
262
 
263
- # Inicializar el chat con el prompt unificado
264
- system_prompt = get_unified_reel_prompt() # Cambiar de get_unified_puv_prompt a get_unified_reel_prompt
265
- if state.chat is not None: # Verificación adicional de seguridad
266
- state.chat.send_message(system_prompt)
267
- else:
268
- st.error("Error: No se pudo inicializar el chat correctamente.")
269
-
270
- # Procesar entrada del usuario
271
- if prompt := st.chat_input('Describe tu audiencia y el objetivo de tu Reel...'):
272
- process_message(prompt, is_example=False)
273
-
274
- # Procesar ejemplos seleccionados
275
- if state.has_prompt():
276
- prompt = state.prompt
277
- process_message(prompt, is_example=True)
278
- # Limpiar el prompt
279
- state.clear_prompt()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import os
3
  import joblib
4
  import streamlit as st
 
5
  from dotenv import load_dotenv
 
6
  from system_prompts import get_unified_reel_prompt # Cambiar de get_unified_puv_prompt a get_unified_reel_prompt
7
+ from reel_formulas import reel_formulas
8
+ from session_state import (
9
+ SessionState,
10
+ DEFAULT_GEMINI_MODEL,
11
+ DATA_DIR,
12
+ PAST_CHATS_LIST_PATH,
13
+ )
14
 
15
  # Inicializar el estado de la sesión
16
  state = SessionState()
17
+ STREAM_SETTINGS = {'batch_size': 1, 'delay_seconds': 0.01}
18
 
19
  # Función para detectar saludos y generar respuestas personalizadas
20
  def is_greeting(text):
 
48
  typing_indicator.markdown("*Generando respuesta...*")
49
 
50
  response = state.send_message(enhanced_prompt)
51
+ full_response = stream_response(response, message_placeholder, typing_indicator, STREAM_SETTINGS)
52
 
53
  if full_response:
54
  state.add_message(MODEL_ROLE, full_response, AI_AVATAR_ICON)
55
+ if hasattr(state.chat, 'get_history'):
56
+ state.gemini_history = state.chat.get_history()
57
+ else:
58
+ state.gemini_history = getattr(state.chat, 'history', [])
59
  state.save_chat_history()
60
 
61
  except Exception as e:
 
71
  past_chats[state.chat_id] = state.chat_title
72
  else:
73
  state.chat_title = past_chats[state.chat_id]
74
+ joblib.dump(past_chats, PAST_CHATS_LIST_PATH)
75
+
76
+ def detect_formula_selection(prompt):
77
+ """Detecta si el usuario eligió una fórmula por nombre o por número."""
78
+ normalized_prompt = prompt.lower().strip()
79
+ formula_names = list(reel_formulas.keys())
80
+
81
+ # Selección por número (1, 2, 3...)
82
+ if normalized_prompt.isdigit():
83
+ formula_index = int(normalized_prompt) - 1
84
+ if 0 <= formula_index < len(formula_names):
85
+ return formula_names[formula_index]
86
+
87
+ # Selección por nombre parcial/completo
88
+ for formula_name in formula_names:
89
+ if formula_name.lower() in normalized_prompt:
90
+ return formula_name
91
+
92
+ return None
93
+
94
+ def get_user_context_for_formula(max_user_messages=6):
95
+ """Recupera contexto reciente del usuario para rellenar la fórmula elegida."""
96
+ recent_user_messages = [
97
+ m['content'] for m in state.messages
98
+ if m.get('role') == 'user'
99
+ ][-max_user_messages:]
100
+ return "\n".join(f"- {message}" for message in recent_user_messages)
101
+
102
+ def build_formula_prompt(formula_name):
103
+ """Construye un prompt estricto usando la fórmula del diccionario."""
104
+ formula_data = reel_formulas[formula_name]
105
+ formula_description = formula_data.get('description', '').strip()
106
+ user_context = get_user_context_for_formula()
107
+
108
+ return f"""
109
+ El usuario eligió explícitamente esta fórmula: "{formula_name}".
110
+
111
+ APLICA ESTRICTAMENTE la siguiente estructura:
112
+ {formula_description}
113
+
114
+ Contexto real del usuario (úsalo para personalizar el guion):
115
+ {user_context if user_context else '- Sin contexto previo suficiente.'}
116
+
117
+ Instrucciones obligatorias de salida:
118
+ 1) Devuelve SOLO el texto final del Reel (sin encabezados ni etiquetas).
119
+ 2) Respeta el orden y los pasos de la fórmula elegida.
120
+ 3) Incluye un gancho potente y un cierre con llamado a la acción.
121
+ 4) Que tenga longitud aproximada de 60 segundos al leer.
122
+ """
123
 
124
  def get_enhanced_prompt(prompt, is_example):
125
  """Genera el prompt mejorado según el tipo de mensaje"""
126
+ selected_formula = detect_formula_selection(prompt)
127
+ if selected_formula:
128
+ st.session_state.selected_formula = selected_formula
129
+ return build_formula_prompt(selected_formula)
130
+
131
  if is_greeting(prompt):
132
  return f"El usuario te ha saludado con '{prompt}'. Preséntate brevemente, explica qué es un Reel y por qué es importante, y haz las 3 preguntas iniciales para comenzar a crear el guion del Reel (audiencia ideal, producto/servicio, y llamado a la acción). Sé amigable, breve y toma la iniciativa como el experto que eres."
133
  elif is_example:
134
  return f"El usuario ha seleccionado un ejemplo: '{prompt}'. Responde de manera conversacional y sencilla, como si estuvieras hablando con un amigo. Evita tecnicismos innecesarios. Enfócate en dar información práctica que ayude al usuario a crear su Reel. Usa ejemplos concretos cuando sea posible. Termina tu respuesta con una pregunta que invite al usuario a compartir información sobre su negocio para poder ayudarle a crear su Reel personalizado."
135
  return prompt
136
 
137
+ def stream_response(response, message_placeholder, typing_indicator, stream_settings):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  """Maneja el streaming de la respuesta"""
139
  full_response = ''
140
+ batch_size = max(1, int(stream_settings.get('batch_size', 24)))
141
+ delay_seconds = max(0.0, float(stream_settings.get('delay_seconds', 0.0)))
142
+ pending_chars = 0
143
+
144
  try:
145
  for chunk in response:
146
  if chunk.text:
147
  for ch in chunk.text:
148
  full_response += ch
149
+ pending_chars += 1
150
+ if pending_chars >= batch_size:
151
+ if delay_seconds:
152
+ time.sleep(delay_seconds)
153
+ message_placeholder.markdown(full_response + '▌')
154
+ pending_chars = 0
155
  except Exception as e:
156
  st.error(f"Error en el streaming: {str(e)}")
157
  return ''
158
+
159
+ if pending_chars > 0:
160
+ if delay_seconds:
161
+ time.sleep(delay_seconds)
162
+ message_placeholder.markdown(full_response + '▌')
163
+
164
  typing_indicator.empty()
165
  message_placeholder.markdown(full_response)
166
  return full_response
 
238
  ]
239
 
240
  # Crear los botones de ejemplo
241
+ selected_prompt = None
242
  cols = st.columns(4)
243
  for idx, ejemplo in enumerate(ejemplos):
244
  with cols[idx]:
245
  if st.button(ejemplo["texto"], key=f"ejemplo_{idx}", help=ejemplo["prompt"]):
246
+ st.session_state.pending_example_prompt = ejemplo["prompt"]
247
+ st.session_state.hide_initial_menu = True
248
  st.rerun()
249
 
250
  # Cargar variables de entorno
251
  load_dotenv()
252
+ GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
253
+ if not GOOGLE_API_KEY:
254
+ st.error("Falta la variable de entorno GOOGLE_API_KEY. Configúrala para continuar.")
255
+ st.stop()
256
 
257
  # Configuración de la aplicación
258
  new_chat_id = f'{time.time()}'
 
260
  AI_AVATAR_ICON = '🤖' # Cambia el emoji por uno de robot para coincidir con tu logo
261
  USER_AVATAR_ICON = '👤' # Añade un avatar para el usuario
262
 
263
+ # Resolver usuario actual (Firebase Auth token -> uid, fallback local)
264
+ firebase_store = FirebaseSessionStore.from_env()
265
+ id_token = st.query_params.get("token")
266
+ firebase_uid = None
267
+ if firebase_store and id_token:
268
+ firebase_uid = firebase_store.verify_id_token(id_token)
269
+ state.set_storage(firebase_uid or state.user_id, firebase_store)
270
+
271
+ user_data_dir = f'{DATA_DIR}/users/{state.user_id}'
272
+ past_chats_path = f'{user_data_dir}/past_chats_list'
273
+
274
  # Crear carpeta de datos si no existe
275
  try:
276
+ os.mkdir(DATA_DIR)
277
+ except FileExistsError:
 
278
  pass
279
 
280
  # Cargar chats anteriores
281
  try:
282
+ past_chats = joblib.load(PAST_CHATS_LIST_PATH)
283
+ except (FileNotFoundError, EOFError):
284
  past_chats = {}
285
 
286
  # Sidebar para seleccionar chats anteriores
287
  with st.sidebar:
288
  st.write('# Chats Anteriores')
289
+
290
  if state.chat_id is None:
291
  state.chat_id = st.selectbox(
292
  label='Selecciona un chat anterior',
 
310
  state.load_chat_history()
311
 
312
  # Inicializar el modelo y el chat
313
+ system_prompt = get_unified_reel_prompt()
314
+ state.initialize_model(DEFAULT_GEMINI_MODEL, api_key=GOOGLE_API_KEY)
315
+ state.initialize_chat(system_instruction=system_prompt) # Siempre inicializar el chat después del modelo
316
 
317
  # Mostrar mensajes del historial
318
  for message in state.messages:
 
322
  ):
323
  st.markdown(message['content'])
324
 
325
+ # Capturar entrada del usuario antes de renderizar el menú inicial
326
+ user_prompt = st.chat_input('Describe tu audiencia y el objetivo de tu Reel...')
 
 
 
 
 
327
 
328
+ if 'pending_example_prompt' not in st.session_state:
329
+ st.session_state.pending_example_prompt = None
330
+
331
+ if 'hide_initial_menu' not in st.session_state:
332
+ st.session_state.hide_initial_menu = False
333
+
334
+ if state.has_messages():
335
+ st.session_state.hide_initial_menu = True
336
+
337
+ # Renderizar menú inicial en un contenedor limpiable
338
+ initial_menu_container = st.container()
339
+ if (
340
+ not st.session_state.hide_initial_menu
341
+ and not state.has_messages()
342
+ and not user_prompt
343
+ and not st.session_state.pending_example_prompt
344
+ ):
345
+ with initial_menu_container:
346
+ display_initial_header()
347
+ display_examples()
348
+
349
+ # Procesar entrada del usuario (oculta el menú inmediatamente)
350
+ if user_prompt:
351
+ st.session_state.hide_initial_menu = True
352
+ initial_menu_container.empty()
353
+ process_message(user_prompt, is_example=False)
354
+
355
+ # Procesar ejemplo seleccionado (oculta el menú inmediatamente)
356
+ if st.session_state.pending_example_prompt:
357
+ initial_menu_container.empty()
358
+ process_message(st.session_state.pending_example_prompt, is_example=True)
359
+ st.session_state.pending_example_prompt = None