File size: 17,277 Bytes
87732b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53ed391
 
 
 
 
 
 
 
 
87732b3
 
 
 
 
53ed391
87732b3
 
53ed391
87732b3
 
53ed391
87732b3
 
 
53ed391
87732b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53ed391
 
 
 
 
 
 
87732b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba5b1bd
 
 
 
53ed391
ba5b1bd
 
 
 
 
53ed391
ba5b1bd
 
 
53ed391
 
ba5b1bd
 
 
87732b3
53ed391
 
 
 
 
87732b3
 
 
 
 
 
 
53ed391
87732b3
 
 
 
 
 
 
 
 
 
 
 
ba5b1bd
 
 
53ed391
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
from dotenv import load_dotenv
import streamlit as st
import os
import google.generativeai as genai
import random
from streamlit import session_state as state
from formulas import headline_formulas
from angles import angles

# Cargar las variables de entorno
load_dotenv()

# Configurar la API de Google
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))

# Fórmulas con ejemplos y explicaciones
# headline_formulas dictionary has been moved to formulas/headline_formulas.py

def generate_headlines(number_of_headlines, target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False):
    # Crear la configuración del modelo
    generation_config = {
        "temperature": temperature,
        "top_p": 0.65,
        "top_k": 360,
        "max_output_tokens": 8196,
    }

    model = genai.GenerativeModel(
        model_name="gemini-2.0-flash",
        generation_config=generation_config,
    )
    
    # Angle dictionaries have been moved to angles/angle_data.py
    
    # Incluir las instrucciones del sistema en el prompt principal
    system_prompt = f"""You are a world-class copywriter, with expertise in crafting hooks, headlines, and subject lines that immediately capture the reader's attention, prompting them to open the email or continue reading.

FORMAT RULES:
- Each headline must start with number and period
- One headline per line
- No explanations or categories
- Add a line break between each headline
- Avoid unnecessary : symbols
- Each headline must be a complete and intriguing sentence

IMPORTANT ANGLE INSTRUCTIONS:
- The selected angle MUST be applied to EVERY headline
- The angle modifies HOW the formula is expressed, not its structure
- Think of the angle as a "tone overlay" on the formula
- The formula provides the structure, the angle provides the style
- Both must work together seamlessly

FORMAT EXAMPLE:
1. Titular 1.

2. Titular 2.

3. Titular 3.

4. Titular 4.

5. Titular 5.

IMPORTANT:
- Each headline must be unique and memorable
- Avoid clichés and generalities
- Maintain an intriguing but credible tone
- Adapt speaking language from the audience
- Focus on transformative benefits
- Follow the selected angle style while maintaining formula structure"""

    # Iniciar el prompt con las instrucciones del sistema
    headlines_instruction = f"{system_prompt}\n\n"

    # Añadir contenido del archivo si existe
    if file_content:
        headlines_instruction += f"""
REFERENCE CONTENT:
Carefully analyze the following content as a reference for generating headlines:
{file_content[:3000]}

ANALYSIS INSTRUCTIONS:
1. Extract key information about the product or service mentioned
2. Identify the tone, style, and language used
3. Detect any data about the target audience or customer avatar
4. Look for benefits, features, or pain points mentioned
5. Use relevant terms, phrases, or concepts from the content
6. Maintain consistency with the brand identity or main message
7. Adapt the headlines to resonate with the provided content
8. Identify any creative elements like characters, movies, cultural references, or storytelling devices
9. Incorporate these creative elements into your headlines when appropriate

CREATIVE CONTEXT GUIDELINES:
- If the content mentions specific characters, incorporate their traits or quotes in headlines
- If movie or pop culture references exist, use similar tone or style in headlines
- Match the emotional tone and creative direction of the original content
- Maintain the same storytelling approach if present in the content
- Use metaphors, analogies or creative devices similar to those in the content

IMPORTANT COMBINATIONS:
"""
        # Updated conditions for specific input combinations
        if product and not target_audience:
            headlines_instruction += f"""- FILE + PRODUCT: You have a reference document and product ({product}). Create headlines that highlight this specific product's benefits and features using insights from the document. Extract audience information from the document to better target the headlines. Ensure headlines match the creative context and tone of the original content.
"""
        elif target_audience and not product:
            headlines_instruction += f"""- FILE + TARGET AUDIENCE: You have a reference document and target audience ({target_audience}). Create headlines tailored to this specific audience using language and concepts from the document. Identify products or services from the document that would appeal to this audience. Maintain the creative direction and contextual elements from the original content.
"""
        elif product and target_audience:
            headlines_instruction += f"""- PRODUCT + TARGET AUDIENCE: You have both product ({product}) and target audience ({target_audience}). Create headlines that connect this specific product with this specific audience, using insights from the document to strengthen the connection. Incorporate any creative elements, characters, or thematic approaches from the original content.
"""
        
        headlines_instruction += """
IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle. If the content has a specific creative direction (storytelling, character-based, movie reference, etc.), ensure your headlines reflect this creative context while maintaining the formula structure.
"""
    
    # Añadir instrucciones de ángulo solo si no es "NINGUNO"
    if selected_angle != "NINGUNO":
        headlines_instruction += f"""
ÁNGULO PRINCIPAL: {selected_angle}
INSTRUCCIONES DE ÁNGULO ESPECÍFICAS:
{angles[selected_angle]["instruction"]}

IMPORTANTE: El ángulo {selected_angle} debe aplicarse como una "capa de estilo" sobre la estructura de la fórmula:
1. Mantén la estructura base de la fórmula intacta
2. Aplica el tono y estilo del ángulo {selected_angle}
3. Asegura que cada elemento de la fórmula refleje el ángulo
4. El ángulo afecta al "cómo" se dice, no al "qué" se dice

EJEMPLOS EXITOSOS DEL ÁNGULO {selected_angle}:
"""
        for example in angles[selected_angle]["examples"]:
            headlines_instruction += f"- {example}\n"

    headlines_instruction += (
        f"\nTu tarea es crear {number_of_headlines} titulares irresistibles para {target_audience} "
        f"que capturen la atención instantáneamente y generen curiosidad sobre {product}. "
    )

    if selected_angle != "NINGUNO":
        headlines_instruction += f"IMPORTANTE: Cada titular DEBE seguir el ángulo {selected_angle} de manera clara y consistente.\n\n"

    headlines_instruction += (
        f"Evita menciones obvias de {product} y enfócate en despertar interés genuino"
    )

    if selected_angle != "NINGUNO":
        headlines_instruction += f" usando el ángulo seleccionado"

    headlines_instruction += ".\n\n"

    headlines_instruction += (
        f"IMPORTANTE: Estudia cuidadosamente estos ejemplos de la fórmula seleccionada. "
        f"Cada ejemplo representa el estilo y estructura a seguir"
    )

    if selected_angle != "NINGUNO":
        headlines_instruction += f", adaptados al ángulo {selected_angle}"

    headlines_instruction += ":\n\n"

    # Agregar 5 ejemplos aleatorios de la fórmula
    random_examples = random.sample(selected_formula['examples'], min(5, len(selected_formula['examples'])))

    headlines_instruction += "EJEMPLOS DE LA FÓRMULA A SEGUIR:\n"
    for i, example in enumerate(random_examples, 1):
        headlines_instruction += f"{i}. {example}\n"

    headlines_instruction += "\nINSTRUCCIONES ESPECÍFICAS:\n"
    headlines_instruction += "1. Mantén la misma estructura y longitud que los ejemplos anteriores\n"
    headlines_instruction += "2. Usa el mismo tono y estilo de escritura\n"
    headlines_instruction += "3. Replica los patrones de construcción de frases\n"
    headlines_instruction += "4. Conserva el nivel de especificidad y detalle\n"
    headlines_instruction += f"5. Adapta el contenido para {target_audience} manteniendo la esencia de los ejemplos\n\n"

    headlines_instruction += f"FÓRMULA A SEGUIR:\n{selected_formula['description']}\n\n"

    # CORRECTO (con indentación):
    if selected_angle != "NINGUNO":
        headlines_instruction += f"""
    RECORDATORIO FINAL:
    1. Sigue la estructura de la fórmula seleccionada
    2. Aplica el ángulo como una "capa de estilo"
    3. Mantén la coherencia entre fórmula y ángulo
    4. Asegura que cada titular refleje ambos elementos

GENERA AHORA:
Crea {number_of_headlines} titulares que sigan fielmente el estilo y estructura de los ejemplos mostrados.
"""
    else:
        headlines_instruction += f"""
GENERA AHORA:
Crea {number_of_headlines} titulares que sigan fielmente el estilo y estructura de los ejemplos mostrados.
"""

    # Modificar la forma de enviar el mensaje según si hay imagen o no
    if is_image and image_parts:
        chat_session = model.start_chat(
            history=[
                {
                    "role": "user",
                    "parts": [
                        headlines_instruction,
                        image_parts
                    ],
                },
            ]
        )
        response = chat_session.send_message("Genera los titulares siguiendo exactamente el estilo de los ejemplos mostrados, inspirándote en la imagen proporcionada.")
    else:
        chat_session = model.start_chat(
            history=[
                {
                    "role": "user",
                    "parts": [headlines_instruction],
                },
            ]
        )
        response = chat_session.send_message("Genera los titulares siguiendo exactamente el estilo de los ejemplos mostrados.")
    
    return response.text

# Configurar la interfaz de usuario con Streamlit
st.set_page_config(page_title="Enchanted Hooks", layout="wide")

# Leer el contenido del archivo manual.md
with open("manual.md", "r", encoding="utf-8") as file:
    manual_content = file.read()

# Mostrar el contenido del manual en el sidebar
st.sidebar.markdown(manual_content)

# Load CSS from file
with open("styles/main.css", "r") as f:
    css = f.read()
    
# Apply the CSS
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)

# Centrar el título y el subtítulo
st.markdown("<h1 style='text-align: center;'>Enchanted Hooks</h1>", unsafe_allow_html=True)
st.markdown("<h4 style='text-align: center;'>Imagina poder conjurar títulos que no solo informan, sino que encantan. Esta app es tu varita mágica en el mundo del copywriting, transformando cada concepto en un titular cautivador que deja a todos deseando más.</h4>", unsafe_allow_html=True)

# Crear columnas
col1, col2 = st.columns([1, 2])  

# Columnas de entrada
with col1:
    target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
    product = st.text_input("¿Qué producto tienes en mente?", placeholder="Ejemplo: Curso de Inglés")
    number_of_headlines = st.selectbox("Número de Titulares", options=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], index=4)

    # Crear un único acordeón para fórmula, creatividad y ángulo
    with st.expander("Personaliza tus titulares"):
        # Añadir área de texto para copy base
        base_copy = st.text_area(
            "Correo o copy para inspiración de titulares",
            placeholder="Agrega aquí un texto que sirva como base para generar los titulares. Si lo dejas vacío, se generarán basados en el producto y público objetivo.",
            height=150
        )
        
        temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)

        selected_formula_key = st.selectbox(
            "Selecciona una fórmula para tus titulares",
            options=list(headline_formulas.keys())
        )

        # Automatically use the keys from the angles dictionary
        # Make sure "NINGUNO" appears first, then the rest alphabetically
        angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
        selected_angle = st.selectbox(
            "Selecciona el ángulo para tus titulares",
            options=angle_keys
        )
        
        # Añadir cargador de archivos dentro del acordeón
        uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia", 
                                        type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
        
        file_content = ""
        is_image = False
        image_parts = None
        
        if uploaded_file is not None:
            file_type = uploaded_file.name.split('.')[-1].lower()
            
            # Manejar archivos de texto
            if file_type in ['txt', 'pdf', 'docx']:
                if file_type == 'txt':
                    try:
                        file_content = uploaded_file.read().decode('utf-8')
                        st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
                    except Exception as e:
                        st.error(f"Error al leer el archivo TXT: {str(e)}")
                        file_content = ""
                
                elif file_type == 'pdf':
                    try:
                        import PyPDF2
                        pdf_reader = PyPDF2.PdfReader(uploaded_file)
                        file_content = ""
                        for page in pdf_reader.pages:
                            file_content += page.extract_text() + "\n"
                        st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
                    except Exception as e:
                        st.error(f"Error al leer el archivo PDF: {str(e)}")
                        file_content = ""
                        
                elif file_type == 'docx':
                    try:
                        import docx
                        doc = docx.Document(uploaded_file)
                        file_content = "\n".join([para.text for para in doc.paragraphs])
                        st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
                    except Exception as e:
                        st.error(f"Error al leer el archivo DOCX: {str(e)}")
                        file_content = ""
            
            # Manejar archivos de imagen
            elif file_type in ['jpg', 'jpeg', 'png']:
                try:
                    from PIL import Image
                    image = Image.open(uploaded_file)
                    image_bytes = uploaded_file.getvalue()
                    image_parts = {
                        "mime_type": uploaded_file.type,
                        "data": image_bytes
                    }
                    is_image = True
                    st.image(image, caption="Imagen cargada", use_column_width=True)
                except Exception as e:
                    st.error(f"Error al procesar la imagen: {str(e)}")
                    is_image = False

    selected_formula = headline_formulas[selected_formula_key]

    # Botón de enviar
    submit = st.button("Generar Titulares")

# Mostrar los titulares generados
if submit:
    # Check if we have a valid combination of inputs
    has_file = 'file_content' in locals() and file_content.strip() != ""
    has_product = product.strip() != ""
    has_audience = target_audience.strip() != ""
    has_base_copy = 'base_copy' in locals() and base_copy.strip() != ""
    
    # Valid combinations:
    # 1. File + Product (no audience needed)
    # 2. File + Audience (no product needed)
    # 3. Product + Audience (traditional way)
    # 4. Base copy alone (new option)
    valid_inputs = (
        (has_file and has_product) or
        (has_file and has_audience) or
        (has_product and has_audience) or
        has_base_copy
    )
    
    if valid_inputs and selected_formula:
        try:
            # If base_copy is provided, use it as file_content
            content_for_generation = file_content if 'file_content' in locals() and file_content.strip() != "" else ""
            if has_base_copy:
                content_for_generation = base_copy
                
            generated_headlines = generate_headlines(
                number_of_headlines,
                target_audience,
                product,
                temperature,
                selected_formula,
                selected_angle,
                content_for_generation,
                image_parts if 'image_parts' in locals() else None,
                is_image if 'is_image' in locals() else False
            )
            col2.markdown(f"""
                <div class="results-container">
                    <h4>Observa la magia en acción:</h4>
                    <p>{generated_headlines}</p>
                </div>
            """, unsafe_allow_html=True)
        except ValueError as e:
            col2.error(f"Error: {str(e)}")
    else:
        if not selected_formula:
            col2.error("Por favor, selecciona una fórmula.")
        else:
            col2.error("Por favor, proporciona al menos una de estas combinaciones: archivo + producto, archivo + público objetivo, producto + público objetivo, o un texto de inspiración.")