| import time |
| import os |
| import joblib |
| import streamlit as st |
| import google.generativeai as genai |
| from dotenv import load_dotenv |
| from reel_formulas import reel_formulas |
| from system_prompts import get_unified_reel_prompt |
| from session_state import SessionState |
|
|
| |
| state = SessionState() |
|
|
| |
| def is_greeting(text): |
| """Detecta si el texto es un saludo simple""" |
| text = text.lower().strip() |
| greetings = ['hola', 'hey', 'saludos', 'buenos días', 'buenas tardes', 'buenas noches', 'hi', 'hello'] |
| |
| |
| |
| is_simple_greeting = any(greeting in text for greeting in greetings) and len(text.split()) < 4 |
| return is_simple_greeting and len(state.messages) == 0 |
|
|
| |
| def process_message(prompt, is_example=False): |
| """Procesa un mensaje del usuario, ya sea directo o de un ejemplo""" |
| handle_chat_title(prompt) |
| |
| with st.chat_message('user', avatar=USER_AVATAR_ICON): |
| st.markdown(prompt) |
| |
| state.add_message('user', prompt, USER_AVATAR_ICON) |
| |
| |
| enhanced_prompt = get_enhanced_prompt(prompt, is_example) |
| |
| |
| with st.chat_message(MODEL_ROLE, avatar=AI_AVATAR_ICON): |
| try: |
| message_placeholder = st.empty() |
| typing_indicator = st.empty() |
| typing_indicator.markdown("*Generando respuesta...*") |
| |
| response = state.send_message(enhanced_prompt) |
| full_response = stream_response(response, message_placeholder, typing_indicator) |
| |
| if full_response: |
| state.add_message(MODEL_ROLE, full_response, AI_AVATAR_ICON) |
| state.gemini_history = state.chat.history |
| state.save_chat_history() |
| |
| except Exception as e: |
| st.error(f"Error en el streaming: {str(e)}") |
| return |
|
|
| def handle_chat_title(prompt): |
| """Maneja la lógica del título del chat""" |
| if state.chat_id not in past_chats: |
| temp_title = f'SesiónChat-{state.chat_id}' |
| generated_title = state.generate_chat_title(prompt) |
| state.chat_title = generated_title or temp_title |
| past_chats[state.chat_id] = state.chat_title |
| else: |
| state.chat_title = past_chats[state.chat_id] |
| joblib.dump(past_chats, 'data/past_chats_list') |
|
|
| def get_enhanced_prompt(prompt, is_example): |
| """Genera el prompt mejorado según el tipo de mensaje""" |
| if is_greeting(prompt): |
| 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." |
| elif is_example: |
| 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." |
| return prompt |
|
|
| def process_model_response(enhanced_prompt): |
| """Procesa la respuesta del modelo""" |
| with st.chat_message(MODEL_ROLE, avatar=AI_AVATAR_ICON): |
| try: |
| message_placeholder = st.empty() |
| typing_indicator = st.empty() |
| typing_indicator.markdown("*Generando respuesta...*") |
| |
| response = state.send_message(enhanced_prompt) |
| full_response = stream_response(response, message_placeholder, typing_indicator) |
| |
| |
| state.add_message(role=MODEL_ROLE, content=full_response, avatar=AI_AVATAR_ICON) |
| state.gemini_history = state.chat.history |
| state.save_chat_history() |
| |
| except Exception as e: |
| st.error(f"Error: {str(e)}") |
|
|
| def stream_response(response, message_placeholder, typing_indicator): |
| """Maneja el streaming de la respuesta""" |
| full_response = '' |
| try: |
| for chunk in response: |
| if chunk.text: |
| for ch in chunk.text: |
| full_response += ch |
| time.sleep(0.01) |
| typing_indicator.markdown("*Generando respuesta...*") |
| message_placeholder.markdown(full_response + '▌') |
| except Exception as e: |
| st.error(f"Error en el streaming: {str(e)}") |
| return '' |
| |
| typing_indicator.empty() |
| message_placeholder.markdown(full_response) |
| return full_response |
|
|
| |
| def load_css(file_path): |
| with open(file_path) as f: |
| st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True) |
|
|
| |
| try: |
| css_path = os.path.join(os.path.dirname(__file__), 'static', 'css', 'style.css') |
| load_css(css_path) |
| except Exception as e: |
| print(f"Error al cargar CSS: {e}") |
| |
| st.markdown(""" |
| <style> |
| .robocopy-title { |
| color: white !important; |
| font-weight: bold; |
| font-size: clamp(2.5em, 5vw, 4em); |
| line-height: 1.2; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| def display_initial_header(): |
| col1, col2, col3 = st.columns([1, 2, 1]) |
| with col2: |
| |
| st.markdown(""" |
| <style> |
| div.stImage { |
| text-align: center; |
| display: block; |
| margin-left: auto; |
| margin-right: auto; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
| st.image("robocopy_logo.png", width=300, use_container_width=True) |
| |
| |
| st.markdown(""" |
| <div style='text-align: center; margin-top: -35px; width: 100%;'> |
| <h1 class='robocopy-title' style='width: 100%; text-align: center; color: white !important; font-size: clamp(2.5em, 5vw, 4em); line-height: 1.2;'>Reel Creator</h1> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| st.markdown(""" |
| <div style='text-align: center; width: 100%;'> |
| <p style='font-size: 16px; color: white; width: 100%; text-align: center; margin-top: -20px;'>By Jesús Cabrera</p> |
| </div> |
| """, unsafe_allow_html=True) |
| |
| |
| st.markdown(""" |
| <div style='text-align: center; width: 100%;'> |
| <p style='font-size: 16px; background-color: transparent; padding: 12px; border-radius: 8px; margin-top: -20px; color: white; width: 100%; text-align: center;'> |
| 🎥 Experto en crear Reels virales que convierten visualizaciones en clientes |
| </p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| def display_examples(): |
| ejemplos = [ |
| {"texto": "¿Cómo crear un Reel efectivo? 🎥", "prompt": "Explícame cómo puedo crear un Reel efectivo que enganche a mi audiencia desde el primer segundo"}, |
| {"texto": "Ideas para Reels de mi negocio 💡", "prompt": "Necesito ideas creativas para crear Reels que promocionen mi negocio y productos"}, |
| {"texto": "Estructura de un buen Reel ✨", "prompt": "¿Cuál es la mejor estructura para crear un Reel que mantenga la atención y genere conversiones?"}, |
| {"texto": "¿Qué fórmula de Reel usar? 🤔", "prompt": "Ayúdame a elegir la fórmula más adecuada para mi Reel según mi tipo de negocio y objetivo"} |
| ] |
|
|
| |
| cols = st.columns(4) |
| for idx, ejemplo in enumerate(ejemplos): |
| with cols[idx]: |
| if st.button(ejemplo["texto"], key=f"ejemplo_{idx}", help=ejemplo["prompt"]): |
| state.prompt = ejemplo["prompt"] |
| st.rerun() |
|
|
| |
| load_dotenv() |
| GOOGLE_API_KEY=os.environ.get('GOOGLE_API_KEY') |
| genai.configure(api_key=GOOGLE_API_KEY) |
|
|
| |
| new_chat_id = f'{time.time()}' |
| MODEL_ROLE = 'ai' |
| AI_AVATAR_ICON = '🤖' |
| USER_AVATAR_ICON = '👤' |
|
|
| |
| try: |
| os.mkdir('data/') |
| except: |
| |
| pass |
|
|
| |
| try: |
| past_chats: dict = joblib.load('data/past_chats_list') |
| except: |
| past_chats = {} |
|
|
| |
| with st.sidebar: |
| st.write('# Chats Anteriores') |
| if state.chat_id is None: |
| state.chat_id = st.selectbox( |
| label='Selecciona un chat anterior', |
| options=[new_chat_id] + list(past_chats.keys()), |
| format_func=lambda x: past_chats.get(x, 'Nuevo Chat'), |
| placeholder='_', |
| ) |
| else: |
| |
| state.chat_id = st.selectbox( |
| label='Selecciona un chat anterior', |
| options=[new_chat_id, state.chat_id] + list(past_chats.keys()), |
| index=1, |
| format_func=lambda x: past_chats.get(x, 'Nuevo Chat' if x != state.chat_id else state.chat_title), |
| placeholder='_', |
| ) |
| |
| state.chat_title = f'SesiónChat-{state.chat_id}' |
|
|
| |
| state.load_chat_history() |
|
|
| |
| state.initialize_model('gemini-3.1-flash-lite-preview') |
| state.initialize_chat() |
|
|
| |
| for message in state.messages: |
| with st.chat_message( |
| name=message['role'], |
| avatar=message.get('avatar'), |
| ): |
| st.markdown(message['content']) |
|
|
| |
| if not state.has_messages(): |
| |
| display_initial_header() |
| |
| |
| display_examples() |
|
|
| |
| system_prompt = get_unified_reel_prompt() |
| if state.chat is not None: |
| state.chat.send_message(system_prompt) |
| else: |
| st.error("Error: No se pudo inicializar el chat correctamente.") |
|
|
| |
| if prompt := st.chat_input('Describe tu audiencia y el objetivo de tu Reel...'): |
| process_message(prompt, is_example=False) |
|
|
| |
| if state.has_prompt(): |
| prompt = state.prompt |
| process_message(prompt, is_example=True) |
| |
| state.clear_prompt() |