JeCabrera commited on
Commit
dbfed54
·
verified ·
1 Parent(s): a10e640

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +584 -597
app.py CHANGED
@@ -1,597 +1,584 @@
1
- from dotenv import load_dotenv
2
- import streamlit as st
3
- import os
4
- import google.generativeai as genai
5
- import random
6
- from streamlit import session_state as state
7
- from formulas import email_formulas
8
- from angles import angles
9
- import re
10
- import datetime
11
- import PyPDF2
12
- import docx
13
- from PIL import Image
14
-
15
- # Cargar las variables de entorno
16
- load_dotenv()
17
-
18
- # Configurar la API de Google
19
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
20
-
21
- # Fórmulas con ejemplos y explicaciones
22
- # email_formulas dictionary has been moved to formulas/email_formulas.py
23
-
24
- # Cambiar el nombre de la función
25
- def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea="", brand_tone=""):
26
- # Crear la configuración del modelo
27
- generation_config = {
28
- "temperature": temperature,
29
- "top_p": 0.65,
30
- "top_k": 360,
31
- "max_output_tokens": 8196,
32
- }
33
-
34
- model = genai.GenerativeModel(
35
- model_name="gemini-2.0-flash",
36
- generation_config=generation_config,
37
- )
38
-
39
- # Definir las instrucciones de formato una sola vez
40
- format_instructions = """
41
- FORMAT RULES:
42
- - Each email must start with "Correo 1", "Correo 2", etc.
43
- - Each email must have a clear and attractive subject line
44
- - The email body must be persuasive and emotional
45
- - Include a clear call to action
46
- - Add a professional signature
47
- - Separate each email clearly
48
- - Do not include greetings like 'Hello [Name]'
49
- - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body
50
- - IMPORTANT: The first email must be an introduction without referencing any previous communications
51
- - Emails 2-5 should build on the sequence in a logical progression
52
- """
53
-
54
- # Incluir las instrucciones del sistema en el prompt principal
55
- system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
56
-
57
- You understand how real people interact with emails in their busy inboxes:
58
- - They quickly scan and delete anything that looks like generic marketing
59
- - They only open emails that feel personal or spark genuine curiosity
60
- - They respond to messages that seem written by a real person, not a corporation
61
- - They engage with content that hooks them from the first line and maintains interest
62
-
63
- Your task is to create a 5-email sequence that makes {target_audience} feel {'' if emotion == 'NINGUNO' else emotion + ' '}about {product} and convince them to {desired_action}.
64
-
65
- AUDIENCE-SPECIFIC LANGUAGE ADAPTATION:
66
- - Analyze the language patterns typical of {target_audience} and mirror them in your writing
67
- - Adjust formality level based on audience demographics (more formal for professionals/executives, more casual for younger audiences)
68
- - Use vocabulary, references, and examples that will resonate specifically with {target_audience}
69
- - Match the communication style preferences of this audience (direct vs indirect, detailed vs concise)
70
- - Include cultural touchpoints or shared experiences relevant to this specific audience
71
- """
72
-
73
- # Add brand tone instructions only if a specific tone is selected
74
- if brand_tone != "NINGUNO":
75
- system_prompt += f"""
76
- BRAND TONE: {brand_tone}
77
- - Write all emails using a consistent {brand_tone} tone of voice
78
- - Ensure language, expressions, and style reflect this tone throughout
79
- - Maintain this tone while still being persuasive and effective
80
- """
81
-
82
- system_prompt += f"""
83
- Each email must follow these four essential actions:
84
- 1. ATTRACT with a subject line that feels personal and creates curiosity (never screaming "Buy me!")
85
- 2. CONNECT through rhythm, emotion, and storytelling that maintains interest until the end
86
- 3. CONVINCE with authentic language that feels believable and relevant
87
- 4. GUIDE to action naturally, making readers click almost without realizing it
88
-
89
- WRITING STYLE:
90
- - Write as if speaking to one person, not an audience
91
- - Vary sentence length deliberately (mix short punchy sentences with occasional longer ones)
92
- - Create natural paragraph rhythm (1-3 sentences per paragraph)
93
- - Use conversational transitions between ideas
94
- - Include specific details that make scenarios feel real
95
-
96
- {format_instructions}
97
-
98
- IMPORTANT:
99
- - Each email must be unique and memorable
100
- - Avoid clichés and generalities
101
- - Maintain a persuasive but credible tone
102
- - Adapt language to the target audience
103
- - Focus on transformative benefits"""
104
-
105
- # Iniciar el prompt con las instrucciones del sistema
106
- email_instruction = f"{system_prompt}\n\n"
107
-
108
- # Add instructions for using the enhanced formula structure
109
- if hasattr(selected_formula, 'get') and selected_formula.get('subject_line_types') and selected_formula.get('email_structure_template'):
110
- email_instruction += """
111
- ENHANCED FORMULA STRUCTURE:
112
- Use the following structure elements to create more effective emails:
113
-
114
- SUBJECT LINE TYPES:
115
- """
116
- # Add subject line types with descriptions
117
- for subject_type, description in selected_formula.get('subject_line_types', {}).items():
118
- email_instruction += f"- {subject_type}: {description}\n"
119
-
120
- # Add technique types (either infotainment or nurturing)
121
- technique_key = next((k for k in selected_formula.keys() if k.endswith('_techniques')), None)
122
- if technique_key:
123
- email_instruction += f"\n{technique_key.upper()}:\n"
124
- for technique, description in selected_formula.get(technique_key, {}).items():
125
- email_instruction += f"- {technique}: {description}\n"
126
-
127
- # Add email structure template
128
- email_instruction += "\nEMAIL STRUCTURE TEMPLATE:\n"
129
- for email_num, structure in selected_formula.get('email_structure_template', {}).items():
130
- email_instruction += f"\n{email_num.upper()}:\n"
131
- email_instruction += f"- Purpose: {structure.get('purpose', '')}\n"
132
- email_instruction += f"- Emotional Focus: {structure.get('emotional_focus', '')}\n"
133
-
134
- # Add recommended subject types
135
- if structure.get('recommended_subject_types'):
136
- email_instruction += "- Recommended Subject Types: " + ", ".join(structure.get('recommended_subject_types', [])) + "\n"
137
-
138
- # Add recommended techniques
139
- if structure.get('recommended_techniques'):
140
- email_instruction += "- Recommended Techniques: " + ", ".join(structure.get('recommended_techniques', [])) + "\n"
141
-
142
- # Add key elements
143
- if structure.get('key_elements'):
144
- email_instruction += "- Key Elements:\n"
145
- for element in structure.get('key_elements', []):
146
- email_instruction += f" * {element}\n"
147
-
148
- # Añadir instrucciones para la idea creativa si existe
149
- if creative_idea:
150
- email_instruction += f"""
151
- CREATIVE CONCEPT:
152
- Use the following creative concept as the central theme for all emails in the sequence:
153
- "{creative_idea}"
154
-
155
- CREATIVE CONCEPT INTEGRATION:
156
- 1. WEAVING TECHNIQUE: Integrate this concept naturally throughout each email:
157
- * Use it as a metaphor or analogy that connects to your product benefits
158
- * Reference it in subject lines to create continuity across the sequence
159
- * Develop different aspects of this concept in each email
160
-
161
- 2. PROGRESSIVE DEVELOPMENT:
162
- * Email 1: Introduce the concept in a simple, intriguing way
163
- * Email 2: Expand on the concept, showing its relevance to the audience's situation
164
- * Email 3: Deepen the concept by connecting it to specific product features
165
- * Email 4: Apply the concept to overcome common objections
166
- * Email 5: Bring the concept full circle, using it to drive the final call to action
167
-
168
- 3. CREATIVE CONCEPT EXAMPLES:
169
- * "Life is a journey" → Email 1 focuses on starting points, Email 5 on destinations
170
- * "Your business as a garden" → Email 1 plants seeds, Email 5 shows full bloom
171
- * "Breaking through walls" Email 1 identifies walls, Email 5 celebrates freedom
172
-
173
- The concept should make your emails more memorable, cohesive, and engaging while naturally leading to your product as the solution.
174
- """
175
-
176
- # Añadir contenido del archivo si existe
177
- if file_content:
178
- email_instruction += f"""
179
- REFERENCE CONTENT:
180
- Carefully analyze the following content as a reference for generating emails:
181
- {file_content[:3000]}
182
-
183
- ANALYSIS INSTRUCTIONS:
184
- 1. Extract key information about the product or service mentioned
185
- 2. Identify the tone, style, and language used
186
- 3. Detect any data about the target audience or customer avatar
187
- 4. Look for benefits, features, or pain points mentioned
188
- 5. Use relevant terms, phrases, or concepts from the content
189
- 6. Maintain consistency with the brand identity or main message
190
- 7. Adapt the emails to resonate with the provided content
191
-
192
- IMPORTANT COMBINATIONS:
193
- """
194
- # Updated conditions for specific input combinations
195
- if product and not target_audience:
196
- email_instruction += f"""- FILE + PRODUCT: You have a reference document and product ({product}). Create emails that highlight this specific product's benefits and features using insights from the document. Extract audience information from the document to better target the emails.
197
- """
198
- elif target_audience and not product:
199
- email_instruction += f"""- FILE + TARGET AUDIENCE: You have a reference document and target audience ({target_audience}). Create emails 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.
200
- """
201
- elif product and target_audience:
202
- email_instruction += f"""- PRODUCT + TARGET AUDIENCE: You have both product ({product}) and target audience ({target_audience}). Create emails that connect this specific product with this specific audience, using insights from the document to strengthen the connection.
203
- """
204
-
205
- email_instruction += """
206
- IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
207
- """
208
-
209
- # Preparar instrucciones específicas del ángulo una sola vez
210
- angle_instructions = ""
211
- if selected_angle != "NINGUNO":
212
- angle_instructions = f"""
213
- MAIN ANGLE: {selected_angle}
214
- SPECIFIC ANGLE INSTRUCTIONS:
215
- {angles[selected_angle]["instruction"]}
216
-
217
- IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
218
- 1. Keep the base structure of the formula intact
219
- 2. Apply the tone and style of the {selected_angle} angle
220
- 3. Ensure each element of the formula reflects the angle
221
- 4. The angle affects "how" it's said, not "what" is said
222
-
223
- SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
224
- """
225
- for example in angles[selected_angle]["examples"]:
226
- angle_instructions += f"- {example}\n"
227
-
228
- # Añadir las instrucciones del ángulo al prompt principal
229
- email_instruction += angle_instructions
230
-
231
- # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
232
- email_instruction += (
233
- f"\nYour task is to create 5 persuasive emails for {target_audience} "
234
- f"that evoke {emotion} and convince them to {desired_action} about {product}. "
235
- )
236
-
237
- # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado
238
- if angle_instructions:
239
- email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently."
240
-
241
- email_instruction += "\n\n"
242
-
243
- # Take the first 5 examples (or all if less than 5) in order
244
- sequential_examples = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
245
-
246
- email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
247
- for i, example in enumerate(sequential_examples, 1):
248
- email_instruction += f"{i}. {example}\n"
249
-
250
- # Añadir instrucciones específicas sobre cómo seguir los ejemplos
251
- email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
252
- email_instruction += "1. Maintain the same structure and length as the previous examples\n"
253
- email_instruction += "2. Use the same tone and writing style\n"
254
- email_instruction += "3. Replicate the phrase construction patterns\n"
255
- email_instruction += "4. Preserve the level of specificity and detail\n"
256
- email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n\n"
257
-
258
- # Añadir instrucciones específicas sobre la temperatura creativa
259
- email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n"
260
-
261
- email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
262
-
263
- # Consolidar las instrucciones finales en un solo bloque
264
- final_reminder = ["Follow the structure of the selected formula"]
265
-
266
- # Add angle-specific instructions only if an angle is selected
267
- if selected_angle != "NINGUNO":
268
- final_reminder.extend([
269
- "Apply the angle as a 'style layer'",
270
- "Maintain coherence between formula and angle",
271
- "Ensure each email reflects both elements"
272
- ])
273
-
274
- email_instruction += "\nFINAL REMINDER:\n"
275
- for i, reminder in enumerate(final_reminder, 1):
276
- email_instruction += f"{i}. {reminder}\n"
277
-
278
- email_instruction += "\nGENERATE NOW:\nCreate 5 emails that faithfully follow the style and structure of the examples shown.\n"
279
-
280
- # Modificar la forma de enviar el mensaje según si hay imagen o no
281
- message_parts = [email_instruction]
282
-
283
- # Build instruction text more clearly with conditional components
284
- instruction_components = ["Generate the emails in Spanish following exactly the style and structure of the examples shown."]
285
-
286
- # Add the image to the message parts if it exists
287
- if is_image and image_parts:
288
- message_parts.append(image_parts)
289
- instruction_components.append("drawing inspiration from the provided image.")
290
-
291
- # Add final instruction
292
- instruction_components.append("Do not include explanations, only the emails.")
293
-
294
- # Join all instruction components with proper spacing
295
- instruction_text = " ".join(instruction_components)
296
-
297
- # Create the chat session with the message parts
298
- try:
299
- chat_session = model.start_chat(
300
- history=[
301
- {
302
- "role": "user",
303
- "parts": message_parts,
304
- },
305
- ]
306
- )
307
-
308
- # Enviar el mensaje con las instrucciones
309
- response = chat_session.send_message(instruction_text)
310
-
311
- return response.text
312
- except genai.types.generation_types.StopCandidateException as e:
313
- # Handle content filtering/safety issues
314
- error_message = f"La generación se detuvo debido a restricciones de contenido: {str(e)}"
315
- st.error(error_message)
316
- return f"Error: {error_message}"
317
- except genai.types.generation_types.BlockedPromptException as e:
318
- # Handle blocked prompt issues
319
- error_message = f"El prompt fue bloqueado por políticas de contenido: {str(e)}"
320
- st.error(error_message)
321
- return f"Error: {error_message}"
322
- except Exception as e:
323
- # Handle general API errors
324
- error_message = f"Error al comunicarse con la API de Google: {str(e)}"
325
- st.error(error_message)
326
- return f"Error: {error_message}"
327
-
328
- # Define the clean_response_text function before it's used
329
- def clean_response_text(text):
330
- """Remove extra spaces and normalize whitespace in the response text"""
331
- # Replace multiple newlines with just two
332
- text = re.sub(r'\n{3,}', '\n\n', text)
333
- # Remove leading/trailing whitespace
334
- text = text.strip()
335
- return text
336
-
337
- # Configurar la interfaz de usuario con Streamlit
338
- st.set_page_config(
339
- page_title="Email Composer",
340
- layout="wide",
341
- menu_items={}, # Empty dict removes the three-dot menu
342
- initial_sidebar_state="expanded"
343
- )
344
-
345
- # Add custom CSS to reduce margins
346
- st.markdown("""
347
- <style>
348
- .main > div {
349
- padding-top: 1rem;
350
- }
351
- </style>
352
- """, unsafe_allow_html=True)
353
-
354
- # Leer el contenido del archivo manual.md
355
- manual_path = "manual.md"
356
- if os.path.exists(manual_path):
357
- with open(manual_path, "r", encoding="utf-8") as file:
358
- manual_content = file.read()
359
- # Mostrar el contenido del manual en el sidebar
360
- st.sidebar.markdown(manual_content)
361
- else:
362
- st.sidebar.warning("Manual file not found.")
363
-
364
- # Load CSS from file
365
- css_path = "styles/main.css"
366
- if os.path.exists(css_path):
367
- with open(css_path, "r") as f:
368
- css = f.read()
369
- # Apply the CSS
370
- st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
371
- else:
372
- st.warning("CSS file not found.")
373
-
374
- # Centrar el título y el subtítulo
375
- st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
376
- st.markdown("<h4 style='text-align: center;'>Transforma tu marketing con emails persuasivos que convierten. Esta aplicación es tu arma secreta para crear emails emocionales de respuesta directa que impulsan a la acción.</h4>", unsafe_allow_html=True)
377
-
378
- # Crear columnas
379
- col1, col2 = st.columns([1, 2])
380
-
381
- # Initialize these variables at the top level before the UI components
382
- file_content = ""
383
- is_image = False
384
- image_parts = None
385
-
386
- # Columnas de entrada
387
- with col1:
388
- target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
389
- product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
390
-
391
- # Move "Acción deseada" outside the accordion
392
- desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
393
-
394
- # Move formula selection here, right after desired action
395
- selected_formula_key = st.selectbox(
396
- "Selecciona una fórmula para tus emails",
397
- options=list(email_formulas.email_formulas.keys()),
398
- key="formula_selectbox" # Add a unique key
399
- )
400
-
401
- # Only one submit button with a unique key
402
- submit = st.button("Generar Emails", key="generate_emails_button")
403
-
404
- # Crear un único acordeón para fórmula, creatividad y ángulo
405
- with st.expander("Personaliza tus emails"):
406
- # 1. Idea Creativa al principio con altura de 70px
407
- creative_idea = st.text_area("Idea Creativa", placeholder="Ejemplo: Tu curso es como Netflix: ofrece contenido que engancha y soluciones que la gente realmente quiere ver", height=70)
408
-
409
- # 2. Lo demás (cargador de archivos)
410
- uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
411
- type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
412
-
413
- file_content = ""
414
- is_image = False
415
- image_parts = None
416
-
417
- if uploaded_file is not None:
418
- # El código para manejar archivos permanece igual
419
- file_type = uploaded_file.name.split('.')[-1].lower()
420
-
421
- # Manejar archivos de texto
422
- if file_type in ['txt', 'pdf', 'docx']:
423
- # El código para manejar archivos de texto permanece igual
424
- # ...
425
- if file_type == 'txt':
426
- try:
427
- file_content = uploaded_file.read().decode('utf-8')
428
- st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
429
- except Exception as e:
430
- st.error(f"Error al leer el archivo TXT: {str(e)}")
431
- file_content = ""
432
-
433
- elif file_type == 'pdf':
434
- try:
435
- import PyPDF2
436
- pdf_reader = PyPDF2.PdfReader(uploaded_file)
437
- file_content = ""
438
- for page in pdf_reader.pages:
439
- file_content += page.extract_text() + "\n"
440
- st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
441
- except Exception as e:
442
- st.error(f"Error al leer el archivo PDF: {str(e)}")
443
- file_content = ""
444
-
445
- elif file_type == 'docx':
446
- try:
447
- import docx
448
- doc = docx.Document(uploaded_file)
449
- file_content = "\n".join([para.text for para in doc.paragraphs])
450
- st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
451
- except Exception as e:
452
- st.error(f"Error al leer el archivo DOCX: {str(e)}")
453
- file_content = ""
454
-
455
- # Manejar archivos de imagen
456
- elif file_type in ['jpg', 'jpeg', 'png']:
457
- try:
458
- from PIL import Image
459
- image = Image.open(uploaded_file)
460
- image_bytes = uploaded_file.getvalue()
461
- image_parts = {
462
- "mime_type": uploaded_file.type,
463
- "data": image_bytes
464
- }
465
- is_image = True
466
- st.image(image, caption="Imagen cargada", use_column_width=True)
467
- except Exception as e:
468
- st.error(f"Error processing image: {str(e)}")
469
- is_image = False
470
-
471
- # 4. Ángulo
472
- angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
473
- selected_angle = st.selectbox(
474
- "Selecciona un ángulo para tus emails",
475
- options=angle_keys,
476
- key="angle_selectbox" # Add a unique key
477
- )
478
-
479
- # 5. Emoción
480
- emotion = st.selectbox(
481
- "¿Qué emoción quieres evocar?",
482
- options=["NINGUNO", "Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
483
- key="emotion_selectbox" # Add a unique key
484
- )
485
-
486
- # Añadir selector de tono de marca
487
- brand_tone = st.selectbox(
488
- "Tono de marca",
489
- options=["NINGUNO", "Profesional", "Conversacional", "Entusiasta", "Formal", "Informal", "Humorístico", "Sarcástico", "Serio", "Inspirador", "Educativo"],
490
- key="brand_tone_selectbox"
491
- )
492
-
493
- # 6. Creatividad (slider)
494
- temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
495
-
496
- selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
497
-
498
- # Removed the submit button from here
499
- # Define a function to validate inputs
500
- def validate_inputs(file_content, product, target_audience, emotion, desired_action):
501
- """
502
- Validates input combinations and returns a tuple of (is_valid, error_message)
503
- """
504
- has_file = file_content.strip() != "" if file_content else False
505
- has_product = product.strip() != ""
506
- has_audience = target_audience.strip() != ""
507
- has_emotion = emotion.strip() != "" if emotion else False
508
- has_action = desired_action.strip() != "" if desired_action else False
509
-
510
- # Check for valid combinations
511
- if has_file and has_product:
512
- return True, "" # File + Product is valid
513
- elif has_file and has_audience:
514
- return True, "" # File + Audience is valid
515
- elif has_product and has_audience and has_emotion and has_action:
516
- return True, "" # Product + Audience + Emotion + Action is valid
517
-
518
- # If we get here, no valid combination was found
519
- if not (has_emotion and has_action):
520
- return False, "Por favor especifica la emoción que quieres evocar y la acción deseada."
521
- else:
522
- return False, "Por favor proporciona al menos una de estas combinaciones: archivo + producto, archivo + público objetivo, o producto + público objetivo + emoción + acción deseada."
523
-
524
- # Mostrar los emails generados
525
- if submit:
526
- # Validate inputs using the new function
527
- is_valid, error_message = validate_inputs(
528
- file_content,
529
- product,
530
- target_audience,
531
- emotion,
532
- desired_action
533
- )
534
-
535
- if is_valid and selected_formula:
536
- try:
537
- # Use spinner within col2 context
538
- with col2:
539
- with st.spinner("Creando los emails..."):
540
- # Update the function call to include brand_tone
541
- generated_emails = generate_emails(
542
- target_audience,
543
- product,
544
- temperature,
545
- selected_formula,
546
- selected_angle,
547
- file_content,
548
- image_parts,
549
- is_image,
550
- emotion,
551
- desired_action,
552
- creative_idea,
553
- brand_tone # Add the brand tone parameter
554
- )
555
-
556
- # Check if the response starts with "Error:"
557
- if generated_emails.startswith("Error:"):
558
- st.error(generated_emails)
559
- else:
560
- # Clean the response text to remove extra spaces
561
- generated_emails = clean_response_text(generated_emails)
562
-
563
- # Get current timestamp for the filename
564
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
565
-
566
- # Add a download button ABOVE the results
567
- st.download_button(
568
- label="DESCARGAR EMAILS",
569
- data=generated_emails,
570
- file_name=f"emails_persuasivos_{timestamp}.txt",
571
- mime="text/plain",
572
- key="download_top" # Unique key for the top button
573
- )
574
-
575
- # Display the generated emails in col2 (still within col2 context)
576
- st.markdown(f"""
577
- <div class="results-container">
578
- <h4>Tus emails persuasivos:</h4>
579
- <p>{generated_emails}</p>
580
- </div>
581
- """, unsafe_allow_html=True)
582
-
583
- # Download button at the bottom (same as before but with a different key)
584
- st.download_button(
585
- label="DESCARGAR EMAILS",
586
- data=generated_emails,
587
- file_name=f"emails_persuasivos_{timestamp}.txt",
588
- mime="text/plain",
589
- key="download_bottom" # Unique key for the bottom button
590
- )
591
- except Exception as e:
592
- col2.error(f"Error: {str(e)}")
593
- else:
594
- if not selected_formula:
595
- col2.error("Por favor selecciona una fórmula.")
596
- else:
597
- col2.error(error_message)
 
1
+ from dotenv import load_dotenv
2
+ import streamlit as st
3
+ import os
4
+ import google.generativeai as genai
5
+ import random
6
+ from streamlit import session_state as state
7
+ from formulas import email_formulas
8
+ from angles import angles
9
+ import re
10
+ import datetime
11
+ import PyPDF2
12
+ import docx
13
+ from PIL import Image
14
+
15
+ # Cargar las variables de entorno
16
+ load_dotenv()
17
+
18
+ # Configurar la API de Google
19
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
20
+
21
+ # Fórmulas con ejemplos y explicaciones
22
+ # email_formulas dictionary has been moved to formulas/email_formulas.py
23
+
24
+ # Cambiar el nombre de la función
25
+ def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea="", brand_tone=""):
26
+ # Crear la configuración del modelo
27
+ generation_config = {
28
+ "temperature": temperature,
29
+ "top_p": 0.65,
30
+ "top_k": 360,
31
+ "max_output_tokens": 8196,
32
+ }
33
+
34
+ model = genai.GenerativeModel(
35
+ model_name="gemini-2.0-flash",
36
+ generation_config=generation_config,
37
+ )
38
+
39
+ # Definir las instrucciones de formato una sola vez
40
+ format_instructions = """
41
+ FORMAT RULES:
42
+ - Each email must start with "Correo 1", "Correo 2", etc.
43
+ - Each email must have a clear and attractive subject line
44
+ - The email body must be persuasive and emotional
45
+ - Include a clear call to action
46
+ - Add a professional signature
47
+ - Separate each email clearly
48
+ - Do not include greetings like 'Hello [Name]'
49
+ - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body
50
+ - IMPORTANT: The first email must be an introduction without referencing any previous communications
51
+ - Emails 2-5 should build on the sequence in a logical progression
52
+ """
53
+
54
+ # Incluir las instrucciones del sistema en el prompt principal
55
+ system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
56
+
57
+ You understand how real people interact with emails in their busy inboxes:
58
+ - They quickly scan and delete anything that looks like generic marketing
59
+ - They only open emails that feel personal or spark genuine curiosity
60
+ - They respond to messages that seem written by a real person, not a corporation
61
+ - They engage with content that hooks them from the first line and maintains interest
62
+
63
+ Your task is to create a 5-email sequence that makes {target_audience} feel {'' if emotion == 'NINGUNO' else emotion + ' '}about {product} and convince them to {desired_action}.
64
+
65
+ AUDIENCE-SPECIFIC LANGUAGE ADAPTATION:
66
+ - Analyze the language patterns typical of {target_audience} and mirror them in your writing
67
+ - Adjust formality level based on audience demographics (more formal for professionals/executives, more casual for younger audiences)
68
+ - Use vocabulary, references, and examples that will resonate specifically with {target_audience}
69
+ - Match the communication style preferences of this audience (direct vs indirect, detailed vs concise)
70
+ - Include cultural touchpoints or shared experiences relevant to this specific audience
71
+ """
72
+
73
+ # Add brand tone instructions only if a specific tone is selected
74
+ if brand_tone != "NINGUNO":
75
+ system_prompt += f"""
76
+ BRAND TONE: {brand_tone}
77
+ - Write all emails using a consistent {brand_tone} tone of voice
78
+ - Ensure language, expressions, and style reflect this tone throughout
79
+ - Maintain this tone while still being persuasive and effective
80
+ """
81
+
82
+ system_prompt += f"""
83
+ Each email must follow these four essential actions:
84
+ 1. ATTRACT with a subject line that feels personal and creates curiosity (never screaming "Buy me!")
85
+ 2. CONNECT through rhythm, emotion, and storytelling that maintains interest until the end
86
+ 3. CONVINCE with authentic language that feels believable and relevant
87
+ 4. GUIDE to action naturally, making readers click almost without realizing it
88
+
89
+ WRITING STYLE:
90
+ - Write as if speaking to one person, not an audience
91
+ - Vary sentence length deliberately (mix short punchy sentences with occasional longer ones)
92
+ - Create natural paragraph rhythm (1-3 sentences per paragraph)
93
+ - Use conversational transitions between ideas
94
+ - Include specific details that make scenarios feel real
95
+
96
+ {format_instructions}
97
+
98
+ IMPORTANT:
99
+ - Each email must be unique and memorable
100
+ - Avoid clichés and generalities
101
+ - Maintain a persuasive but credible tone
102
+ - Adapt language to the target audience
103
+ - Focus on transformative benefits"""
104
+
105
+ # Iniciar el prompt con las instrucciones del sistema
106
+ email_instruction = f"{system_prompt}\n\n"
107
+
108
+ # Add instructions for using the enhanced formula structure
109
+ if hasattr(selected_formula, 'get') and selected_formula.get('subject_line_types') and selected_formula.get('email_structure_template'):
110
+ email_instruction += """
111
+ ENHANCED FORMULA STRUCTURE:
112
+ Use the following structure elements to create more effective emails:
113
+
114
+ SUBJECT LINE TYPES:
115
+ """
116
+ # Add subject line types with descriptions
117
+ for subject_type, description in selected_formula.get('subject_line_types', {}).items():
118
+ email_instruction += f"- {subject_type}: {description}\n"
119
+
120
+ # Add technique types (either infotainment or nurturing)
121
+ technique_key = next((k for k in selected_formula.keys() if k.endswith('_techniques')), None)
122
+ if technique_key:
123
+ email_instruction += f"\n{technique_key.upper()}:\n"
124
+ for technique, description in selected_formula.get(technique_key, {}).items():
125
+ email_instruction += f"- {technique}: {description}\n"
126
+
127
+ # Add email structure template
128
+ email_instruction += "\nEMAIL STRUCTURE TEMPLATE:\n"
129
+ for email_num, structure in selected_formula.get('email_structure_template', {}).items():
130
+ email_instruction += f"\n{email_num.upper()}:\n"
131
+ email_instruction += f"- Purpose: {structure.get('purpose', '')}\n"
132
+ email_instruction += f"- Emotional Focus: {structure.get('emotional_focus', '')}\n"
133
+
134
+ # Add recommended subject types
135
+ if structure.get('recommended_subject_types'):
136
+ email_instruction += "- Recommended Subject Types: " + ", ".join(structure.get('recommended_subject_types', [])) + "\n"
137
+
138
+ # Add recommended techniques
139
+ if structure.get('recommended_techniques'):
140
+ email_instruction += "- Recommended Techniques: " + ", ".join(structure.get('recommended_techniques', [])) + "\n"
141
+
142
+ # Add key elements
143
+ if structure.get('key_elements'):
144
+ email_instruction += "- Key Elements:\n"
145
+ for element in structure.get('key_elements', []):
146
+ email_instruction += f" * {element}\n"
147
+
148
+ # Añadir instrucciones para la idea creativa si existe
149
+ if creative_idea:
150
+ email_instruction += f"""
151
+ CREATIVE CONCEPT:
152
+ Use the following creative concept as the central theme for all emails in the sequence:
153
+ "{creative_idea}"
154
+
155
+ CREATIVE CONCEPT INSTRUCTIONS:
156
+ 1. This concept should be the unifying theme across all emails
157
+ 2. Use it as a metaphor or analogy throughout the sequence
158
+ 3. Develop different aspects of this concept in each email
159
+ 4. Make sure the concept naturally connects to the product benefits
160
+ 5. The concept should make the emails more memorable and engaging
161
+ """
162
+
163
+ # Añadir contenido del archivo si existe
164
+ if file_content:
165
+ email_instruction += f"""
166
+ REFERENCE CONTENT:
167
+ Carefully analyze the following content as a reference for generating emails:
168
+ {file_content[:3000]}
169
+
170
+ ANALYSIS INSTRUCTIONS:
171
+ 1. Extract key information about the product or service mentioned
172
+ 2. Identify the tone, style, and language used
173
+ 3. Detect any data about the target audience or customer avatar
174
+ 4. Look for benefits, features, or pain points mentioned
175
+ 5. Use relevant terms, phrases, or concepts from the content
176
+ 6. Maintain consistency with the brand identity or main message
177
+ 7. Adapt the emails to resonate with the provided content
178
+
179
+ IMPORTANT COMBINATIONS:
180
+ """
181
+ # Updated conditions for specific input combinations
182
+ if product and not target_audience:
183
+ email_instruction += f"""- FILE + PRODUCT: You have a reference document and product ({product}). Create emails that highlight this specific product's benefits and features using insights from the document. Extract audience information from the document to better target the emails.
184
+ """
185
+ elif target_audience and not product:
186
+ email_instruction += f"""- FILE + TARGET AUDIENCE: You have a reference document and target audience ({target_audience}). Create emails 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.
187
+ """
188
+ elif product and target_audience:
189
+ email_instruction += f"""- PRODUCT + TARGET AUDIENCE: You have both product ({product}) and target audience ({target_audience}). Create emails that connect this specific product with this specific audience, using insights from the document to strengthen the connection.
190
+ """
191
+
192
+ email_instruction += """
193
+ IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
194
+ """
195
+
196
+ # Preparar instrucciones específicas del ángulo una sola vez
197
+ angle_instructions = ""
198
+ if selected_angle != "NINGUNO":
199
+ angle_instructions = f"""
200
+ MAIN ANGLE: {selected_angle}
201
+ SPECIFIC ANGLE INSTRUCTIONS:
202
+ {angles[selected_angle]["instruction"]}
203
+
204
+ IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
205
+ 1. Keep the base structure of the formula intact
206
+ 2. Apply the tone and style of the {selected_angle} angle
207
+ 3. Ensure each element of the formula reflects the angle
208
+ 4. The angle affects "how" it's said, not "what" is said
209
+
210
+ SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
211
+ """
212
+ for example in angles[selected_angle]["examples"]:
213
+ angle_instructions += f"- {example}\n"
214
+
215
+ # Añadir las instrucciones del ángulo al prompt principal
216
+ email_instruction += angle_instructions
217
+
218
+ # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
219
+ email_instruction += (
220
+ f"\nYour task is to create 5 persuasive emails for {target_audience} "
221
+ f"that evoke {emotion} and convince them to {desired_action} about {product}. "
222
+ )
223
+
224
+ # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado
225
+ if angle_instructions:
226
+ email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently."
227
+
228
+ email_instruction += "\n\n"
229
+
230
+ # Take the first 5 examples (or all if less than 5) in order
231
+ sequential_examples = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
232
+
233
+ email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
234
+ for i, example in enumerate(sequential_examples, 1):
235
+ email_instruction += f"{i}. {example}\n"
236
+
237
+ # Añadir instrucciones específicas sobre cómo seguir los ejemplos
238
+ email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
239
+ email_instruction += "1. Maintain the same structure and length as the previous examples\n"
240
+ email_instruction += "2. Use the same tone and writing style\n"
241
+ email_instruction += "3. Replicate the phrase construction patterns\n"
242
+ email_instruction += "4. Preserve the level of specificity and detail\n"
243
+ email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n\n"
244
+
245
+ # Añadir instrucciones específicas sobre la temperatura creativa
246
+ email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n"
247
+
248
+ email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
249
+
250
+ # Consolidar las instrucciones finales en un solo bloque
251
+ final_reminder = ["Follow the structure of the selected formula"]
252
+
253
+ # Add angle-specific instructions only if an angle is selected
254
+ if selected_angle != "NINGUNO":
255
+ final_reminder.extend([
256
+ "Apply the angle as a 'style layer'",
257
+ "Maintain coherence between formula and angle",
258
+ "Ensure each email reflects both elements"
259
+ ])
260
+
261
+ email_instruction += "\nFINAL REMINDER:\n"
262
+ for i, reminder in enumerate(final_reminder, 1):
263
+ email_instruction += f"{i}. {reminder}\n"
264
+
265
+ email_instruction += "\nGENERATE NOW:\nCreate 5 emails that faithfully follow the style and structure of the examples shown.\n"
266
+
267
+ # Modificar la forma de enviar el mensaje según si hay imagen o no
268
+ message_parts = [email_instruction]
269
+
270
+ # Build instruction text more clearly with conditional components
271
+ instruction_components = ["Generate the emails in Spanish following exactly the style and structure of the examples shown."]
272
+
273
+ # Add the image to the message parts if it exists
274
+ if is_image and image_parts:
275
+ message_parts.append(image_parts)
276
+ instruction_components.append("drawing inspiration from the provided image.")
277
+
278
+ # Add final instruction
279
+ instruction_components.append("Do not include explanations, only the emails.")
280
+
281
+ # Join all instruction components with proper spacing
282
+ instruction_text = " ".join(instruction_components)
283
+
284
+ # Create the chat session with the message parts
285
+ try:
286
+ chat_session = model.start_chat(
287
+ history=[
288
+ {
289
+ "role": "user",
290
+ "parts": message_parts,
291
+ },
292
+ ]
293
+ )
294
+
295
+ # Enviar el mensaje con las instrucciones
296
+ response = chat_session.send_message(instruction_text)
297
+
298
+ return response.text
299
+ except genai.types.generation_types.StopCandidateException as e:
300
+ # Handle content filtering/safety issues
301
+ error_message = f"La generación se detuvo debido a restricciones de contenido: {str(e)}"
302
+ st.error(error_message)
303
+ return f"Error: {error_message}"
304
+ except genai.types.generation_types.BlockedPromptException as e:
305
+ # Handle blocked prompt issues
306
+ error_message = f"El prompt fue bloqueado por políticas de contenido: {str(e)}"
307
+ st.error(error_message)
308
+ return f"Error: {error_message}"
309
+ except Exception as e:
310
+ # Handle general API errors
311
+ error_message = f"Error al comunicarse con la API de Google: {str(e)}"
312
+ st.error(error_message)
313
+ return f"Error: {error_message}"
314
+
315
+ # Define the clean_response_text function before it's used
316
+ def clean_response_text(text):
317
+ """Remove extra spaces and normalize whitespace in the response text"""
318
+ # Replace multiple newlines with just two
319
+ text = re.sub(r'\n{3,}', '\n\n', text)
320
+ # Remove leading/trailing whitespace
321
+ text = text.strip()
322
+ return text
323
+
324
+ # Configurar la interfaz de usuario con Streamlit
325
+ st.set_page_config(
326
+ page_title="Email Composer",
327
+ layout="wide",
328
+ menu_items={}, # Empty dict removes the three-dot menu
329
+ initial_sidebar_state="expanded"
330
+ )
331
+
332
+ # Add custom CSS to reduce margins
333
+ st.markdown("""
334
+ <style>
335
+ .main > div {
336
+ padding-top: 1rem;
337
+ }
338
+ </style>
339
+ """, unsafe_allow_html=True)
340
+
341
+ # Leer el contenido del archivo manual.md
342
+ manual_path = "manual.md"
343
+ if os.path.exists(manual_path):
344
+ with open(manual_path, "r", encoding="utf-8") as file:
345
+ manual_content = file.read()
346
+ # Mostrar el contenido del manual en el sidebar
347
+ st.sidebar.markdown(manual_content)
348
+ else:
349
+ st.sidebar.warning("Manual file not found.")
350
+
351
+ # Load CSS from file
352
+ css_path = "styles/main.css"
353
+ if os.path.exists(css_path):
354
+ with open(css_path, "r") as f:
355
+ css = f.read()
356
+ # Apply the CSS
357
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
358
+ else:
359
+ st.warning("CSS file not found.")
360
+
361
+ # Centrar el título y el subtítulo
362
+ st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
363
+ st.markdown("<h4 style='text-align: center;'>Transforma tu marketing con emails persuasivos que convierten. Esta aplicación es tu arma secreta para crear emails emocionales de respuesta directa que impulsan a la acción.</h4>", unsafe_allow_html=True)
364
+
365
+ # Crear columnas
366
+ col1, col2 = st.columns([1, 2])
367
+
368
+ # Initialize these variables at the top level before the UI components
369
+ file_content = ""
370
+ is_image = False
371
+ image_parts = None
372
+
373
+ # Columnas de entrada
374
+ with col1:
375
+ target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
376
+ product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
377
+
378
+ # Move "Acción deseada" outside the accordion
379
+ desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
380
+
381
+ # Move formula selection here, right after desired action
382
+ selected_formula_key = st.selectbox(
383
+ "Selecciona una fórmula para tus emails",
384
+ options=list(email_formulas.email_formulas.keys()),
385
+ key="formula_selectbox" # Add a unique key
386
+ )
387
+
388
+ # Only one submit button with a unique key
389
+ submit = st.button("Generar Emails", key="generate_emails_button")
390
+
391
+ # Crear un único acordeón para fórmula, creatividad y ángulo
392
+ with st.expander("Personaliza tus emails"):
393
+ # 1. Idea Creativa al principio con altura de 70px
394
+ creative_idea = st.text_area("Idea Creativa", placeholder="Ejemplo: Tu curso es como Netflix: ofrece contenido que engancha y soluciones que la gente realmente quiere ver", height=70)
395
+
396
+ # 2. Lo demás (cargador de archivos)
397
+ uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
398
+ type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
399
+
400
+ file_content = ""
401
+ is_image = False
402
+ image_parts = None
403
+
404
+ if uploaded_file is not None:
405
+ # El código para manejar archivos permanece igual
406
+ file_type = uploaded_file.name.split('.')[-1].lower()
407
+
408
+ # Manejar archivos de texto
409
+ if file_type in ['txt', 'pdf', 'docx']:
410
+ # El código para manejar archivos de texto permanece igual
411
+ # ...
412
+ if file_type == 'txt':
413
+ try:
414
+ file_content = uploaded_file.read().decode('utf-8')
415
+ st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
416
+ except Exception as e:
417
+ st.error(f"Error al leer el archivo TXT: {str(e)}")
418
+ file_content = ""
419
+
420
+ elif file_type == 'pdf':
421
+ try:
422
+ import PyPDF2
423
+ pdf_reader = PyPDF2.PdfReader(uploaded_file)
424
+ file_content = ""
425
+ for page in pdf_reader.pages:
426
+ file_content += page.extract_text() + "\n"
427
+ st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
428
+ except Exception as e:
429
+ st.error(f"Error al leer el archivo PDF: {str(e)}")
430
+ file_content = ""
431
+
432
+ elif file_type == 'docx':
433
+ try:
434
+ import docx
435
+ doc = docx.Document(uploaded_file)
436
+ file_content = "\n".join([para.text for para in doc.paragraphs])
437
+ st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
438
+ except Exception as e:
439
+ st.error(f"Error al leer el archivo DOCX: {str(e)}")
440
+ file_content = ""
441
+
442
+ # Manejar archivos de imagen
443
+ elif file_type in ['jpg', 'jpeg', 'png']:
444
+ try:
445
+ from PIL import Image
446
+ image = Image.open(uploaded_file)
447
+ image_bytes = uploaded_file.getvalue()
448
+ image_parts = {
449
+ "mime_type": uploaded_file.type,
450
+ "data": image_bytes
451
+ }
452
+ is_image = True
453
+ st.image(image, caption="Imagen cargada", use_column_width=True)
454
+ except Exception as e:
455
+ st.error(f"Error processing image: {str(e)}")
456
+ is_image = False
457
+
458
+ # 4. Ángulo
459
+ angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
460
+ selected_angle = st.selectbox(
461
+ "Selecciona un ángulo para tus emails",
462
+ options=angle_keys,
463
+ key="angle_selectbox" # Add a unique key
464
+ )
465
+
466
+ # 5. Emoción
467
+ emotion = st.selectbox(
468
+ "¿Qué emoción quieres evocar?",
469
+ options=["NINGUNO", "Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
470
+ key="emotion_selectbox" # Add a unique key
471
+ )
472
+
473
+ # Añadir selector de tono de marca
474
+ brand_tone = st.selectbox(
475
+ "Tono de marca",
476
+ options=["NINGUNO", "Profesional", "Conversacional", "Entusiasta", "Formal", "Informal", "Humorístico", "Sarcástico", "Serio", "Inspirador", "Educativo"],
477
+ key="brand_tone_selectbox"
478
+ )
479
+
480
+ # 6. Creatividad (slider)
481
+ temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
482
+
483
+ selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
484
+
485
+ # Removed the submit button from here
486
+ # Define a function to validate inputs
487
+ def validate_inputs(file_content, product, target_audience, emotion, desired_action):
488
+ """
489
+ Validates input combinations and returns a tuple of (is_valid, error_message)
490
+ """
491
+ has_file = file_content.strip() != "" if file_content else False
492
+ has_product = product.strip() != ""
493
+ has_audience = target_audience.strip() != ""
494
+ has_emotion = emotion.strip() != "" if emotion else False
495
+ has_action = desired_action.strip() != "" if desired_action else False
496
+
497
+ # Check for valid combinations
498
+ if has_file and has_product:
499
+ return True, "" # File + Product is valid
500
+ elif has_file and has_audience:
501
+ return True, "" # File + Audience is valid
502
+ elif has_product and has_audience and has_emotion and has_action:
503
+ return True, "" # Product + Audience + Emotion + Action is valid
504
+
505
+ # If we get here, no valid combination was found
506
+ if not (has_emotion and has_action):
507
+ return False, "Por favor especifica la emoción que quieres evocar y la acción deseada."
508
+ else:
509
+ return False, "Por favor proporciona al menos una de estas combinaciones: archivo + producto, archivo + público objetivo, o producto + público objetivo + emoción + acción deseada."
510
+
511
+ # Mostrar los emails generados
512
+ if submit:
513
+ # Validate inputs using the new function
514
+ is_valid, error_message = validate_inputs(
515
+ file_content,
516
+ product,
517
+ target_audience,
518
+ emotion,
519
+ desired_action
520
+ )
521
+
522
+ if is_valid and selected_formula:
523
+ try:
524
+ # Use spinner within col2 context
525
+ with col2:
526
+ with st.spinner("Creando los emails..."):
527
+ # Update the function call to include brand_tone
528
+ generated_emails = generate_emails(
529
+ target_audience,
530
+ product,
531
+ temperature,
532
+ selected_formula,
533
+ selected_angle,
534
+ file_content,
535
+ image_parts,
536
+ is_image,
537
+ emotion,
538
+ desired_action,
539
+ creative_idea,
540
+ brand_tone # Add the brand tone parameter
541
+ )
542
+
543
+ # Check if the response starts with "Error:"
544
+ if generated_emails.startswith("Error:"):
545
+ st.error(generated_emails)
546
+ else:
547
+ # Clean the response text to remove extra spaces
548
+ generated_emails = clean_response_text(generated_emails)
549
+
550
+ # Get current timestamp for the filename
551
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
552
+
553
+ # Add a download button ABOVE the results
554
+ st.download_button(
555
+ label="DESCARGAR EMAILS",
556
+ data=generated_emails,
557
+ file_name=f"emails_persuasivos_{timestamp}.txt",
558
+ mime="text/plain",
559
+ key="download_top" # Unique key for the top button
560
+ )
561
+
562
+ # Display the generated emails in col2 (still within col2 context)
563
+ st.markdown(f"""
564
+ <div class="results-container">
565
+ <h4>Tus emails persuasivos:</h4>
566
+ <p>{generated_emails}</p>
567
+ </div>
568
+ """, unsafe_allow_html=True)
569
+
570
+ # Download button at the bottom (same as before but with a different key)
571
+ st.download_button(
572
+ label="DESCARGAR EMAILS",
573
+ data=generated_emails,
574
+ file_name=f"emails_persuasivos_{timestamp}.txt",
575
+ mime="text/plain",
576
+ key="download_bottom" # Unique key for the bottom button
577
+ )
578
+ except Exception as e:
579
+ col2.error(f"Error: {str(e)}")
580
+ else:
581
+ if not selected_formula:
582
+ col2.error("Por favor selecciona una fórmula.")
583
+ else:
584
+ col2.error(error_message)