File size: 17,320 Bytes
09b8bec
 
25d1401
 
cbb526b
 
8f8ad65
d3c442c
09b8bec
d3c442c
 
 
 
cbb526b
 
 
 
8f8ad65
25d1401
09b8bec
25d1401
09b8bec
 
25d1401
 
d3c442c
09b8bec
 
 
 
25d1401
09b8bec
 
c0b0dd7
c82a36c
c0b0dd7
 
 
 
 
 
 
5596c1e
 
 
 
 
 
 
c82a36c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0b0dd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51329b3
 
 
c0b0dd7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51329b3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c0b0dd7
 
 
 
 
09b8bec
 
 
c0b0dd7
 
 
 
645e9ba
 
 
 
 
09b8bec
cbb526b
c0b0dd7
d3c442c
09b8bec
 
 
d3c442c
 
 
c0b0dd7
 
 
25d1401
09b8bec
cbb526b
 
bed04bb
cbb526b
645e9ba
 
 
 
 
cbb526b
 
 
 
 
 
09b8bec
 
 
0de7167
 
 
 
 
 
 
 
 
 
 
09b8bec
 
 
 
 
 
 
 
 
 
 
0de7167
 
 
09b8bec
 
 
 
 
 
 
 
 
a151314
 
 
5a284ec
cbb526b
f29535c
cbb526b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70cd3d2
 
09b8bec
d3c442c
 
 
 
a151314
09b8bec
d3c442c
b0b32cd
d3c442c
 
 
 
a151314
09b8bec
d3c442c
 
 
 
 
 
a151314
d3c442c
a151314
d3c442c
 
 
 
 
 
 
a151314
d3c442c
 
a151314
d3c442c
 
09b8bec
f29535c
a151314
09b8bec
a151314
09b8bec
f29535c
 
 
 
 
 
 
09b8bec
d3c442c
 
c206df9
5596c1e
3623ca3
5596c1e
c206df9
 
a151314
c20a805
3623ca3
5596c1e
c206df9
 
5596c1e
 
3623ca3
 
 
 
 
 
 
 
 
 
c0b0dd7
c20a805
5596c1e
 
a151314
c20a805
 
 
5596c1e
cbb526b
c82a36c
5596c1e
c0b0dd7
3623ca3
c20a805
a151314
c20a805
 
 
2406c37
 
3ae81e1
3623ca3
 
 
 
 
 
 
c0b0dd7
3623ca3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
09b8bec
c82a36c
5596c1e
b689cfd
 
 
3623ca3
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
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
from dotenv import load_dotenv
import streamlit as st
import os
import google.generativeai as genai
import time
import datetime
import random  # Add this import
from ads_formulas import ads_formulas  # Import the ads formulas
from style import styles
from prompts import create_fb_ad_instruction
from emotional_angles import emotional_angles
from copywriter_personas import copywriter_personas
from ad_objectives import ad_objectives  # Import ad objectives
import PyPDF2
import docx
from PIL import Image
import io
from headline_generator import generate_random_headlines, generate_headlines_with_model, headline_formulas  # Import headline_formulas too

# Cargar las variables de entorno
load_dotenv()

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

# Función para generar modelo
@st.cache_resource
def get_model(temperature):
    generation_config = {
        "temperature": temperature,
    }
    return genai.GenerativeModel('gemini-2.0-flash', generation_config=generation_config)

# Function to generate headlines and select one randomly
def generate_and_select_headline(target_audience, product, temperature, selected_angle, file_content="", image_parts=None):
    # Get the model
    model = get_model(temperature)
    
    # Select a random formula for the headline
    formula_name = random.choice(list(headline_formulas.keys()))
    selected_formula = headline_formulas[formula_name]
    
    # Create instruction with file content if available
    headline_instruction = f"Generate 3 compelling headlines for {product} targeting {target_audience}. Use this formula: {selected_formula}. Apply this emotional angle: {selected_angle}."
    
    # Add file content as context if available
    if file_content:
        headline_instruction += f"\n\nUse the following information as additional context:\n{file_content[:5000]}"
    
    # Generate headlines with or without image
    if image_parts:
        # If we have an image, use it for headline generation
        response = model.generate_content([headline_instruction, image_parts], generation_config={"temperature": temperature})
        headlines = response.parts[0].text if response and response.parts else ""
    else:
        # Generate 3 headlines using text only
        headlines = generate_headlines_with_model(
            model=model,
            target_audience=target_audience,
            product=product,
            selected_formula=selected_formula,
            selected_angle=selected_angle,
            number_of_headlines=3
        )
    
    # Parse the headlines (assuming they come in a numbered format)
    headline_list = []
    for line in headlines.strip().split('\n'):
        if line.strip() and line[0].isdigit() and '.' in line:
            # Extract the headline text after the number and period
            headline_text = line.split('.', 1)[1].strip()
            headline_list.append(headline_text)
    
    # If we couldn't parse any headlines, return a default one
    if not headline_list:
        return f"Descubre cómo {product} puede transformar tu vida como {target_audience}"
    
    # Select a random headline from the generated ones
    selected_headline = random.choice(headline_list)
    return selected_headline

# Move the clean_ad_text function up with other utility functions
# After the generate_headline function and before generate_fb_ad

def generate_headline(target_audience=None, product=None, temperature=1.0, angle=None):
    # Check if UI variables exist, otherwise use parameters
    try:
        target_audience = target_audience or ad_target_audience
        product = product or ad_product
        temperature = temperature or ad_temperature
        angle = angle or emotional_angle_key
    except NameError:
        # If UI variables don't exist yet, use the provided parameters
        pass
    
    # Obtener el modelo usando la función existente
    model = get_model(temperature)
    
    # Remove duplicate imports and use the ones at the top of the file
    formula_name = random.choice(list(headline_formulas.keys()))
    selected_formula = headline_formulas[formula_name]
    
    # Generar el titular usando el modelo ya inicializado
    headline = generate_headlines_with_model(
        model=model,
        target_audience=target_audience,
        product=product,
        selected_formula=selected_formula,
        selected_angle=angle
    )
    
    return headline

# Function to clean generated ad text
def clean_ad_text(text):
    """
    Cleans the generated ad text by removing unwanted characters and formatting issues.
    
    Args:
        text (str): The raw generated ad text
        
    Returns:
        str: Cleaned ad text
    """
    if not isinstance(text, str):
        return ""
    
    # Split text into lines
    lines = text.split('\n')
    
    # Clean lines
    cleaned_lines = []
    for line in lines:
        # Skip lines that are just a single letter
        if len(line.strip()) == 1 and line.strip().isalpha():
            continue
            
        # Remove triple backticks and language indicators (like ```text)
        if line.strip().startswith('```'):
            line = line.replace('```', '').strip()
            # If line now only contains a language indicator, skip it
            if line.strip() in ['text', 'markdown', 'md', 'html']:
                continue
                
        # Remove any other markdown artifacts
        line = line.replace('```', '')
        
        cleaned_lines.append(line)
    
    # Join lines back together
    cleaned_text = '\n'.join(cleaned_lines)
    
    # Remove any consecutive newlines (more than 2)
    import re
    cleaned_text = re.sub(r'\n{3,}', '\n\n', cleaned_text)
    
    return cleaned_text.strip()
    
# Modify the generate_fb_ad function to accept a headline parameter
def generate_fb_ad(target_audience, product, temperature, selected_formula, selected_angle, 
                  selected_persona, story_prompt="", ad_objective=None, file_content="", 
                  image_parts=None, max_file_chars=50000, headline=None):
    if not target_audience or not product:
        return "Por favor, completa todos los campos requeridos."
    
    # Generate a headline if none is provided
    if not headline:
        headline = generate_and_select_headline(target_audience, product, temperature, selected_angle)
    
    # Enfatizar el tema de la historia si se proporciona
    emphasized_story_prompt = story_prompt
    if story_prompt and story_prompt.strip():
        emphasized_story_prompt = story_prompt.strip()
    
    model = get_model(temperature)
    
    # Crear la instrucción base, ahora incluyendo el titular
    ad_instruction = create_fb_ad_instruction(
        target_audience, 
        product, 
        selected_formula,
        selected_angle,
        selected_persona,
        ad_objective,
        language="español",
        story_prompt=emphasized_story_prompt,
        headline=headline  # Pass the headline to the instruction creator
    )
    
    # Si hay contenido de archivo, añadirlo a la instrucción
    if file_content:
        ad_instruction += f"\n\nAdemás, utiliza la siguiente información como referencia para crear el anuncio:\n\n{file_content[:max_file_chars]}"
    
    # Si hay un tema específico, ajustar la temperatura para mayor coherencia
    effective_temperature = temperature
    if story_prompt and story_prompt.strip():
        effective_temperature = max(0.1, temperature * 0.9)
    
    # Generar el contenido con o sin imagen
    if image_parts:
        response = model.generate_content([ad_instruction, image_parts], generation_config={"temperature": effective_temperature})
    else:
        response = model.generate_content([ad_instruction], generation_config={"temperature": effective_temperature})
    
    return response.parts[0].text if response and response.parts else "Error generating content."

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

# Ocultar el menú de tres puntos de Streamlit
hide_menu_style = """
        <style>
        #MainMenu {visibility: hidden;}
        header {visibility: hidden;}
        footer {visibility: hidden;}
        </style>
        """
st.markdown(hide_menu_style, unsafe_allow_html=True)

# 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)

# Ocultar elementos de la interfaz
st.markdown(styles["main_layout"], unsafe_allow_html=True)

# Centrar el título y el subtítulo con el nuevo texto
st.markdown("<h1 style='text-align: center;'>CopyLegend AI</h1>", unsafe_allow_html=True)
st.markdown("<h4 style='text-align: center;'>La única herramienta que combina la sabiduría de los maestros del copywriting con la precisión de la inteligencia artificial</h4>", unsafe_allow_html=True)

# Añadir CSS personalizado para el botón
st.markdown(styles["button"], unsafe_allow_html=True)

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

# Columnas de entrada
with col1:
    ad_target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Madres trabajadoras de 30-45 años")
    ad_product = st.text_input("¿Qué producto tienes en mente?", placeholder="Ejemplo: Curso de gestión del tiempo")
    input_prompt = st.text_area("Escribe de qué quieres que trate la historia:", placeholder="Escribe aquí tu idea...")
    
    # Añadir cargador de archivos
    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')
                except Exception as e:
                    st.error(f"Error al leer el archivo TXT: {str(e)}")
                    file_content = ""
                
            elif file_type == 'pdf':
                try:
                    pdf_reader = PyPDF2.PdfReader(uploaded_file)
                    file_content = ""
                    for page in pdf_reader.pages:
                        file_content += page.extract_text() + "\n"
                except Exception as e:
                    st.error(f"Error al leer el archivo PDF: {str(e)}")
                    file_content = ""
                    
            elif file_type == 'docx':
                try:
                    doc = docx.Document(uploaded_file)
                    file_content = "\n".join([para.text for para in doc.paragraphs])
                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:
                image = Image.open(uploaded_file)
                image_bytes = uploaded_file.getvalue()
                image_parts = [
                    {
                        "mime_type": uploaded_file.type,
                        "data": image_bytes
                    }
                ]
                is_image = True
            except Exception as e:
                st.error(f"Error al procesar la imagen: {str(e)}")
                is_image = False
    
    # Mover el botón aquí, después del cargador de archivos
    submit_ad = st.button("GENERAR ANUNCIO")
    
    with st.expander("Opciones avanzadas"):
        # Selector de fórmula de anuncio
        ad_formula_key = st.selectbox(
            "Fórmula de anuncio",
            options=list(ads_formulas.keys()),
            label_visibility="visible"
        )
        ad_formula = ads_formulas[ad_formula_key]
        
        # Selector de ángulo emocional
        emotional_angle_key = st.selectbox(
            "Ángulo emocional",
            options=list(emotional_angles.keys()),
            label_visibility="visible"
        )
        emotional_angle = emotional_angles[emotional_angle_key]
        
        # Selector de personalidad de copywriter
        ad_persona_key = st.selectbox(
            "Estilo de copywriter",
            options=list(copywriter_personas.keys()),
            index=0,
            format_func=lambda x: x.replace("_", " "),
            label_visibility="visible"
        )
        ad_persona = copywriter_personas[ad_persona_key]
        
        # Selector de objetivo de anuncio
        ad_objective_key = st.selectbox(
            "Objetivo de la campaña",
            options=list(ad_objectives.keys()),
            label_visibility="visible"
        )
        selected_objective = ad_objectives[ad_objective_key] if ad_objective_key != "ninguno" else None

        ad_temperature = st.slider(
            "Nivel de creatividad",
            min_value=0.0, 
            max_value=1.5,  # Reduced from 2.0 to 1.5 for better policy compliance
            value=1.0, 
            step=0.1,
            label_visibility="visible"
        )
        
        # Add disclaimer about content review
        st.markdown("""
        <div style='font-size: 0.8em; color: #888;'>
        Nota: Todo contenido generado debe ser revisado para asegurar cumplimiento con las políticas de Facebook antes de publicar.
        </div>
        """, unsafe_allow_html=True)

# Mostrar el anuncio generado
if submit_ad:
    # Check for different valid input combinations
    has_file_content = file_content.strip() != "" or is_image
    has_basic_fields = ad_target_audience and ad_product
    
    # Generate ad if either file is uploaded OR basic fields are filled
    if has_file_content or has_basic_fields:
        # Mostrar el spinner en la segunda columna
        with col2:
            with st.spinner('Generando anuncio...'):
                # Use default values if fields are empty
                target_audience = ad_target_audience if ad_target_audience else "usuarios interesados"
                product = ad_product if ad_product else "este producto/servicio"
                story = input_prompt if input_prompt else ""
                
                # Generate a headline first, now passing file_content and image_parts
                headline = generate_and_select_headline(
                    target_audience,
                    product,
                    ad_temperature,
                    emotional_angle,
                    file_content,  # Pass file content to headline generation
                    image_parts if is_image else None  # Pass image data if available
                )
                
                # Generate the ad with the headline
                generated_ad = generate_fb_ad(
                    target_audience, 
                    product, 
                    ad_temperature, 
                    ad_formula,
                    emotional_angle,
                    ad_persona,
                    story,
                    selected_objective,
                    file_content,
                    image_parts if is_image else None,
                    50000,
                    headline
                )
                
                if not isinstance(generated_ad, str):
                    st.error("Error al generar el anuncio")
                else:
                    # Clean the generated ad text
                    cleaned_ad = clean_ad_text(generated_ad)
                    
                    # Display only the ad without separate headline and without storing in session state
                    st.markdown(f"""
                        <div style="{styles['results_container']}">
                            <h3>Anuncio Generado:</h3>
                            <p>{cleaned_ad}</p>
                        </div>
                    """, unsafe_allow_html=True)
                    
                    # Aplicar estilo para el botón de descarga
                    st.markdown(styles["download_button"], unsafe_allow_html=True)
                    
                    # Get current timestamp for the filename
                    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
                    
                    # Prepare content for download (without separate headline)
                    download_content = f"{cleaned_ad}"
                    
                    # Botón de descarga with timestamp in filename
                    st.download_button(
                        label="DESCARGAR ANUNCIO",
                        data=download_content,
                        file_name=f"anuncio_facebook_{timestamp}.txt",
                        mime="text/plain"
                    )
    else:
        col2.warning("Por favor, sube un archivo o completa al menos uno de los campos principales.")
# Remove the section that displays cached ads

# Agregar firma del autor al final de la página
st.markdown('---')
st.markdown('Made with ❤️ by Jesús Cabrera')