File size: 11,412 Bytes
c76bbac
 
 
 
e75a09b
c76bbac
a339470
939772b
956fbae
 
 
 
348d961
a316975
 
 
 
 
20ce570
 
 
 
 
a316975
 
 
 
df0841b
a316975
 
 
50670b9
a316975
50670b9
b945abf
 
5a16f21
b945abf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
df0841b
 
 
 
 
 
 
 
 
 
 
 
 
 
a316975
df0841b
60180b4
df0841b
 
 
5ec6171
 
 
 
 
 
 
 
 
b945abf
5ec6171
b945abf
 
 
 
5ec6171
 
 
923a333
df0841b
 
 
9524460
df0841b
 
 
 
b945abf
5ec6171
df0841b
 
 
 
 
 
 
 
a316975
b1cf5e1
348d961
b1cf5e1
 
 
 
 
 
 
 
 
fa342d1
 
 
 
c0c55b0
fa342d1
c0c55b0
 
fa342d1
 
 
c0073a3
956fbae
 
9bd45f9
 
5722b78
 
 
 
 
 
 
 
 
 
 
e61bf12
5722b78
540dd96
5722b78
 
c0c55b0
f6214f2
5722b78
 
a0d27a3
5722b78
 
c069900
f6214f2
5722b78
11b06cd
c069900
11b06cd
 
c069900
11b06cd
 
 
 
956fbae
 
 
dc6ebfc
e0af504
 
 
 
dc6ebfc
 
 
 
 
 
 
956fbae
dc6ebfc
 
956fbae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b194d60
956fbae
b194d60
956fbae
 
 
 
 
 
 
 
 
 
ea00e74
956fbae
 
650485d
956fbae
 
 
939772b
 
b194d60
 
 
 
97cb8fc
956fbae
97cb8fc
a316975
d5a01fe
956fbae
 
 
a316975
956fbae
9524460
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import time
import os
import joblib
import streamlit as st
import google.generativeai as genai
from dotenv import load_dotenv
from puv_formulas import puv_formulas
from system_prompts import get_unified_puv_prompt
from session_state import SessionState

# Inicializar el estado de la sesión
state = SessionState()

# Función para detectar saludos y generar respuestas personalizadas
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']
    
    # Solo considerar como saludo si es el primer mensaje del usuario
    # y es un saludo simple
    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

# Función para procesar mensajes (unifica la lógica de procesamiento)
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)
    
    # Obtener el prompt mejorado primero
    enhanced_prompt = get_enhanced_prompt(prompt, is_example)
    
    # Mover la respuesta del modelo después del mensaje del usuario
    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 una PUV en 1-2 líneas, y haz 1-2 preguntas iniciales para comenzar a crear la PUV del usuario (como a quién se dirige su producto/servicio o qué ofrece). 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 PUV. 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 PUV personalizada."
    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)
            
            # Actualizar historial
            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

# Función para cargar CSS personalizado
def load_css(file_path):
    with open(file_path) as f:
        st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)

# Intentar cargar el CSS personalizado con ruta absoluta para mayor seguridad
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}")
    # Si el archivo no existe, crear un estilo básico en línea
    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)

# Función de utilidad para mostrar la carátula inicial
def display_initial_header():
    col1, col2, col3 = st.columns([1, 2, 1])
    with col2:
        # Centrar la imagen
        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)
        
        # Título con diseño responsivo (eliminado el símbolo ∞)
        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;'>PUV Creator</h1>
            </div>
        """, unsafe_allow_html=True)
        
        # Subtítulo con margen superior ajustado a -30px
        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)
    
    # Descripción con fondo eliminado y margen superior ajustado a -20px
    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 Propuestas de Valor Únicas que convierten audiencia en clientes
            </p>
        </div>
    """, unsafe_allow_html=True)

# Función para mostrar ejemplos de preguntas
def display_examples():
    ejemplos = [
        {"texto": "¿Qué es una Propuesta de Valor Única? 🎯", "prompt": "Explícame qué es una Propuesta de Valor Única (PUV) y por qué es importante para mi negocio"},
        {"texto": "¿Cómo puedo crear mi PUV? 📝", "prompt": "Guíame paso a paso en el proceso de crear una Propuesta de Valor Única efectiva"},
        {"texto": "¿Qué elementos debe tener mi PUV? ✨", "prompt": "¿Cuáles son los elementos esenciales que debe incluir una Propuesta de Valor Única exitosa?"},
        {"texto": "¿Cuál es la mejor fórmula para mi caso? 🤔", "prompt": "Ayúdame a elegir la fórmula más adecuada para mi Propuesta de Valor según mi tipo de negocio"}
    ]

    # Crear los botones de ejemplo
    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()

# Cargar variables de entorno
load_dotenv()
GOOGLE_API_KEY=os.environ.get('GOOGLE_API_KEY')
genai.configure(api_key=GOOGLE_API_KEY)

# Configuración de la aplicación
new_chat_id = f'{time.time()}'
MODEL_ROLE = 'ai'
AI_AVATAR_ICON = '🤖'  # Cambia el emoji por uno de robot para coincidir con tu logo
USER_AVATAR_ICON = '👤'  # Añade un avatar para el usuario

# Crear carpeta de datos si no existe
try:
    os.mkdir('data/')
except:
    # data/ folder already exists
    pass

# Cargar chats anteriores
try:
    past_chats: dict = joblib.load('data/past_chats_list')
except:
    past_chats = {}

# Sidebar para seleccionar chats anteriores
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:
        # This will happen the first time AI response comes in
        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='_',
        )
    # Save new chats after a message has been sent to AI
    state.chat_title = f'SesiónChat-{state.chat_id}'

# Cargar historial del chat
state.load_chat_history()

# Inicializar el modelo y el chat
state.initialize_model('gemini-2.0-flash')
state.initialize_chat()  # Siempre inicializar el chat después del modelo

# Mostrar mensajes del historial
for message in state.messages:
    with st.chat_message(
        name=message['role'],
        avatar=message.get('avatar'),
    ):
        st.markdown(message['content'])

# Mensaje inicial del sistema si es un chat nuevo
if not state.has_messages():
    # Mostrar la carátula inicial con el logo centrado
    display_initial_header()
    
    # Mostrar los ejemplos
    display_examples()

    # Inicializar el chat con el prompt unificado
    system_prompt = get_unified_puv_prompt()
    if state.chat is not None:  # Verificación adicional de seguridad
        state.chat.send_message(system_prompt)
    else:
        st.error("Error: No se pudo inicializar el chat correctamente.")

# Procesar entrada del usuario
if prompt := st.chat_input('Describe tu producto/servicio y audiencia objetivo...'):
    process_message(prompt, is_example=False)

# Procesar ejemplos seleccionados
if state.has_prompt():
    prompt = state.prompt
    process_message(prompt, is_example=True)
    # Limpiar el prompt
    state.clear_prompt()