JeCabrera commited on
Commit
fab438f
·
verified ·
1 Parent(s): 7b6d051

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +539 -499
app.py CHANGED
@@ -1,499 +1,539 @@
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=""):
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 have a clear and attractive subject line
43
- - The email body must be persuasive and emotional
44
- - Include a clear call to action
45
- - Add a professional signature
46
- - Separate each email clearly
47
- - Do not include greetings like 'Hello [Name]'
48
- - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body
49
- - IMPORTANT: The first email must be an introduction without referencing any previous communications
50
- - Emails 2-5 should build on the sequence in a logical progression
51
- """
52
-
53
- # Incluir las instrucciones del sistema en el prompt principal
54
- system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
55
-
56
- You have helped many marketers before me persuade their clients through emotional email sequences.
57
- Your task is to create a 5-email sequence that makes my [buyer persona] feel [emotion] about my [product/service] and convince them to register/take [desired action].
58
-
59
- {format_instructions}
60
-
61
- IMPORTANT:
62
- - Each email must be unique and memorable
63
- - Avoid clichés and generalities
64
- - Maintain a persuasive but credible tone
65
- - Adapt language to the target audience
66
- - Focus on transformative benefits"""
67
-
68
- # Iniciar el prompt con las instrucciones del sistema
69
- email_instruction = f"{system_prompt}\n\n"
70
-
71
- # Añadir instrucciones para la idea creativa si existe
72
- if creative_idea:
73
- email_instruction += f"""
74
- CREATIVE CONCEPT:
75
- Use the following creative concept as the central theme for all emails in the sequence:
76
- "{creative_idea}"
77
-
78
- CREATIVE CONCEPT INSTRUCTIONS:
79
- 1. This concept should be the unifying theme across all emails
80
- 2. Use it as a metaphor or analogy throughout the sequence
81
- 3. Develop different aspects of this concept in each email
82
- 4. Make sure the concept naturally connects to the product benefits
83
- 5. The concept should make the emails more memorable and engaging
84
- """
85
-
86
- # Añadir contenido del archivo si existe
87
- if file_content:
88
- email_instruction += f"""
89
- REFERENCE CONTENT:
90
- Carefully analyze the following content as a reference for generating emails:
91
- {file_content[:3000]}
92
-
93
- ANALYSIS INSTRUCTIONS:
94
- 1. Extract key information about the product or service mentioned
95
- 2. Identify the tone, style, and language used
96
- 3. Detect any data about the target audience or customer avatar
97
- 4. Look for benefits, features, or pain points mentioned
98
- 5. Use relevant terms, phrases, or concepts from the content
99
- 6. Maintain consistency with the brand identity or main message
100
- 7. Adapt the emails to resonate with the provided content
101
-
102
- IMPORTANT COMBINATIONS:
103
- """
104
- # Updated conditions for specific input combinations
105
- if product and not target_audience:
106
- 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.
107
- """
108
- elif target_audience and not product:
109
- 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.
110
- """
111
- elif product and target_audience:
112
- 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.
113
- """
114
-
115
- email_instruction += """
116
- IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
117
- """
118
-
119
- # Preparar instrucciones específicas del ángulo una sola vez
120
- angle_instructions = ""
121
- if selected_angle != "NINGUNO":
122
- angle_instructions = f"""
123
- MAIN ANGLE: {selected_angle}
124
- SPECIFIC ANGLE INSTRUCTIONS:
125
- {angles[selected_angle]["instruction"]}
126
-
127
- IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
128
- 1. Keep the base structure of the formula intact
129
- 2. Apply the tone and style of the {selected_angle} angle
130
- 3. Ensure each element of the formula reflects the angle
131
- 4. The angle affects "how" it's said, not "what" is said
132
-
133
- SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
134
- """
135
- for example in angles[selected_angle]["examples"]:
136
- angle_instructions += f"- {example}\n"
137
-
138
- # Añadir las instrucciones del ángulo al prompt principal
139
- email_instruction += angle_instructions
140
-
141
- # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
142
- email_instruction += (
143
- f"\nYour task is to create 5 persuasive emails for {target_audience} "
144
- f"that evoke {emotion} and convince them to {desired_action} about {product}. "
145
- )
146
-
147
- # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado
148
- if angle_instructions:
149
- email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently."
150
-
151
- email_instruction += "\n\n"
152
-
153
- # Take the first 5 examples (or all if less than 5) in order
154
- sequential_examples = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
155
-
156
- email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
157
- for i, example in enumerate(sequential_examples, 1):
158
- email_instruction += f"{i}. {example}\n"
159
-
160
- # Añadir instrucciones específicas sobre cómo seguir los ejemplos
161
- email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
162
- email_instruction += "1. Maintain the same structure and length as the previous examples\n"
163
- email_instruction += "2. Use the same tone and writing style\n"
164
- email_instruction += "3. Replicate the phrase construction patterns\n"
165
- email_instruction += "4. Preserve the level of specificity and detail\n"
166
- email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n\n"
167
-
168
- # Añadir instrucciones específicas sobre la temperatura creativa
169
- email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n"
170
-
171
- email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
172
-
173
- # Consolidar las instrucciones finales en un solo bloque
174
- final_reminder = ["Follow the structure of the selected formula"]
175
-
176
- # Add angle-specific instructions only if an angle is selected
177
- if selected_angle != "NINGUNO":
178
- final_reminder.extend([
179
- "Apply the angle as a 'style layer'",
180
- "Maintain coherence between formula and angle",
181
- "Ensure each email reflects both elements"
182
- ])
183
-
184
- email_instruction += "\nFINAL REMINDER:\n"
185
- for i, reminder in enumerate(final_reminder, 1):
186
- email_instruction += f"{i}. {reminder}\n"
187
-
188
- email_instruction += "\nGENERATE NOW:\nCreate 5 emails that faithfully follow the style and structure of the examples shown.\n"
189
-
190
- # Modificar la forma de enviar el mensaje según si hay imagen o no
191
- message_parts = [email_instruction]
192
-
193
- # Build instruction text more clearly with conditional components
194
- instruction_components = ["Generate the emails in Spanish following exactly the style and structure of the examples shown."]
195
-
196
- # Add the image to the message parts if it exists
197
- if is_image and image_parts:
198
- message_parts.append(image_parts)
199
- instruction_components.append("drawing inspiration from the provided image.")
200
-
201
- # Add final instruction
202
- instruction_components.append("Do not include explanations, only the emails.")
203
-
204
- # Join all instruction components with proper spacing
205
- instruction_text = " ".join(instruction_components)
206
-
207
- # Create the chat session with the message parts
208
- try:
209
- chat_session = model.start_chat(
210
- history=[
211
- {
212
- "role": "user",
213
- "parts": message_parts,
214
- },
215
- ]
216
- )
217
-
218
- # Enviar el mensaje con las instrucciones
219
- response = chat_session.send_message(instruction_text)
220
-
221
- return response.text
222
- except genai.types.generation_types.StopCandidateException as e:
223
- # Handle content filtering/safety issues
224
- error_message = f"La generación se detuvo debido a restricciones de contenido: {str(e)}"
225
- st.error(error_message)
226
- return f"Error: {error_message}"
227
- except genai.types.generation_types.BlockedPromptException as e:
228
- # Handle blocked prompt issues
229
- error_message = f"El prompt fue bloqueado por políticas de contenido: {str(e)}"
230
- st.error(error_message)
231
- return f"Error: {error_message}"
232
- except Exception as e:
233
- # Handle general API errors
234
- error_message = f"Error al comunicarse con la API de Google: {str(e)}"
235
- st.error(error_message)
236
- return f"Error: {error_message}"
237
-
238
- # Define the clean_response_text function before it's used
239
- def clean_response_text(text):
240
- """Remove extra spaces and normalize whitespace in the response text"""
241
- # Replace multiple newlines with just two
242
- text = re.sub(r'\n{3,}', '\n\n', text)
243
- # Remove leading/trailing whitespace
244
- text = text.strip()
245
- return text
246
-
247
- # Configurar la interfaz de usuario con Streamlit
248
- st.set_page_config(
249
- page_title="Email Composer",
250
- layout="wide",
251
- menu_items={}, # Empty dict removes the three-dot menu
252
- initial_sidebar_state="expanded"
253
- )
254
-
255
- # Add custom CSS to reduce margins
256
- st.markdown("""
257
- <style>
258
- .main > div {
259
- padding-top: 1rem;
260
- }
261
- </style>
262
- """, unsafe_allow_html=True)
263
-
264
- # Leer el contenido del archivo manual.md
265
- manual_path = "manual.md"
266
- if os.path.exists(manual_path):
267
- with open(manual_path, "r", encoding="utf-8") as file:
268
- manual_content = file.read()
269
- # Mostrar el contenido del manual en el sidebar
270
- st.sidebar.markdown(manual_content)
271
- else:
272
- st.sidebar.warning("Manual file not found.")
273
-
274
- # Load CSS from file
275
- css_path = "styles/main.css"
276
- if os.path.exists(css_path):
277
- with open(css_path, "r") as f:
278
- css = f.read()
279
- # Apply the CSS
280
- st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
281
- else:
282
- st.warning("CSS file not found.")
283
-
284
- # Centrar el título y el subtítulo
285
- st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
286
- 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)
287
-
288
- # Crear columnas
289
- col1, col2 = st.columns([1, 2])
290
-
291
- # Initialize these variables at the top level before the UI components
292
- file_content = ""
293
- is_image = False
294
- image_parts = None
295
-
296
- # Columnas de entrada
297
- with col1:
298
- target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
299
- product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
300
-
301
- # Move "Acción deseada" outside the accordion
302
- desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
303
-
304
- # Move formula selection here, right after desired action
305
- selected_formula_key = st.selectbox(
306
- "Selecciona una fórmula para tus emails",
307
- options=list(email_formulas.email_formulas.keys()),
308
- key="formula_selectbox" # Add a unique key
309
- )
310
-
311
- # Only one submit button with a unique key
312
- submit = st.button("Generar Emails", key="generate_emails_button")
313
-
314
- # Crear un único acordeón para fórmula, creatividad y ángulo
315
- with st.expander("Personaliza tus emails"):
316
- # 1. Idea Creativa al principio con altura de 70px
317
- 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)
318
-
319
- # 2. Lo demás (cargador de archivos)
320
- uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
321
- type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
322
-
323
- file_content = ""
324
- is_image = False
325
- image_parts = None
326
-
327
- if uploaded_file is not None:
328
- # El código para manejar archivos permanece igual
329
- file_type = uploaded_file.name.split('.')[-1].lower()
330
-
331
- # Manejar archivos de texto
332
- if file_type in ['txt', 'pdf', 'docx']:
333
- # El código para manejar archivos de texto permanece igual
334
- # ...
335
- if file_type == 'txt':
336
- try:
337
- file_content = uploaded_file.read().decode('utf-8')
338
- st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
339
- except Exception as e:
340
- st.error(f"Error al leer el archivo TXT: {str(e)}")
341
- file_content = ""
342
-
343
- elif file_type == 'pdf':
344
- try:
345
- import PyPDF2
346
- pdf_reader = PyPDF2.PdfReader(uploaded_file)
347
- file_content = ""
348
- for page in pdf_reader.pages:
349
- file_content += page.extract_text() + "\n"
350
- st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
351
- except Exception as e:
352
- st.error(f"Error al leer el archivo PDF: {str(e)}")
353
- file_content = ""
354
-
355
- elif file_type == 'docx':
356
- try:
357
- import docx
358
- doc = docx.Document(uploaded_file)
359
- file_content = "\n".join([para.text for para in doc.paragraphs])
360
- st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
361
- except Exception as e:
362
- st.error(f"Error al leer el archivo DOCX: {str(e)}")
363
- file_content = ""
364
-
365
- # Manejar archivos de imagen
366
- elif file_type in ['jpg', 'jpeg', 'png']:
367
- try:
368
- from PIL import Image
369
- image = Image.open(uploaded_file)
370
- image_bytes = uploaded_file.getvalue()
371
- image_parts = {
372
- "mime_type": uploaded_file.type,
373
- "data": image_bytes
374
- }
375
- is_image = True
376
- st.image(image, caption="Imagen cargada", use_column_width=True)
377
- except Exception as e:
378
- st.error(f"Error processing image: {str(e)}")
379
- is_image = False
380
-
381
- # 4. Ángulo
382
- angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
383
- selected_angle = st.selectbox(
384
- "Selecciona un ángulo para tus emails",
385
- options=angle_keys,
386
- key="angle_selectbox" # Add a unique key
387
- )
388
-
389
- # 5. Emoción
390
- emotion = st.selectbox(
391
- "¿Qué emoción quieres evocar?",
392
- options=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
393
- key="emotion_selectbox" # Add a unique key
394
- )
395
-
396
- # 6. Creatividad (slider)
397
- temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
398
-
399
- selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
400
-
401
- # Removed the submit button from here
402
- # Define a function to validate inputs
403
- def validate_inputs(file_content, product, target_audience, emotion, desired_action):
404
- """
405
- Validates input combinations and returns a tuple of (is_valid, error_message)
406
- """
407
- has_file = file_content.strip() != "" if file_content else False
408
- has_product = product.strip() != ""
409
- has_audience = target_audience.strip() != ""
410
- has_emotion = emotion.strip() != "" if emotion else False
411
- has_action = desired_action.strip() != "" if desired_action else False
412
-
413
- # Check for valid combinations
414
- if has_file and has_product:
415
- return True, "" # File + Product is valid
416
- elif has_file and has_audience:
417
- return True, "" # File + Audience is valid
418
- elif has_product and has_audience and has_emotion and has_action:
419
- return True, "" # Product + Audience + Emotion + Action is valid
420
-
421
- # If we get here, no valid combination was found
422
- if not (has_emotion and has_action):
423
- return False, "Por favor especifica la emoción que quieres evocar y la acción deseada."
424
- else:
425
- 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."
426
-
427
- # Mostrar los emails generados
428
- if submit:
429
- # Validate inputs using the new function
430
- is_valid, error_message = validate_inputs(
431
- file_content,
432
- product,
433
- target_audience,
434
- emotion,
435
- desired_action
436
- )
437
-
438
- if is_valid and selected_formula:
439
- try:
440
- # Use spinner within col2 context
441
- with col2:
442
- with st.spinner("Creando los emails..."):
443
- # Update the function call to include creative_idea
444
- generated_emails = generate_emails(
445
- target_audience,
446
- product,
447
- temperature,
448
- selected_formula,
449
- selected_angle,
450
- file_content,
451
- image_parts,
452
- is_image,
453
- emotion,
454
- desired_action,
455
- creative_idea # Add the creative idea parameter
456
- )
457
-
458
- # Check if the response starts with "Error:"
459
- if generated_emails.startswith("Error:"):
460
- st.error(generated_emails)
461
- else:
462
- # Clean the response text to remove extra spaces
463
- generated_emails = clean_response_text(generated_emails)
464
-
465
- # Get current timestamp for the filename
466
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
467
-
468
- # Add a download button ABOVE the results
469
- st.download_button(
470
- label="DESCARGAR EMAILS",
471
- data=generated_emails,
472
- file_name=f"emails_persuasivos_{timestamp}.txt",
473
- mime="text/plain",
474
- key="download_top" # Unique key for the top button
475
- )
476
-
477
- # Display the generated emails in col2 (still within col2 context)
478
- st.markdown(f"""
479
- <div class="results-container">
480
- <h4>Tus emails persuasivos:</h4>
481
- <p>{generated_emails}</p>
482
- </div>
483
- """, unsafe_allow_html=True)
484
-
485
- # Download button at the bottom (same as before but with a different key)
486
- st.download_button(
487
- label="DESCARGAR EMAILS",
488
- data=generated_emails,
489
- file_name=f"emails_persuasivos_{timestamp}.txt",
490
- mime="text/plain",
491
- key="download_bottom" # Unique key for the bottom button
492
- )
493
- except Exception as e:
494
- col2.error(f"Error: {str(e)}")
495
- else:
496
- if not selected_formula:
497
- col2.error("Por favor selecciona una fórmula.")
498
- else:
499
- 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=""):
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 have a clear and attractive subject line
43
+ - The email body must be persuasive and emotional
44
+ - Include a clear call to action
45
+ - Add a professional signature
46
+ - Separate each email clearly
47
+ - Do not include greetings like 'Hello [Name]'
48
+ - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body
49
+ - IMPORTANT: The first email must be an introduction without referencing any previous communications
50
+ - Emails 2-5 should build on the sequence in a logical progression
51
+ """
52
+
53
+ # Incluir las instrucciones del sistema en el prompt principal
54
+ system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
55
+
56
+ You have helped many marketers before me persuade their clients through emotional email sequences.
57
+ Your task is to create a 5-email sequence that makes my [buyer persona] feel [emotion] about my [product/service] and convince them to register/take [desired action].
58
+
59
+ {format_instructions}
60
+
61
+ IMPORTANT:
62
+ - Each email must be unique and memorable
63
+ - Avoid clichés and generalities
64
+ - Maintain a persuasive but credible tone
65
+ - Adapt language to the target audience
66
+ - Focus on transformative benefits"""
67
+
68
+ # Iniciar el prompt con las instrucciones del sistema
69
+ email_instruction = f"{system_prompt}\n\n"
70
+
71
+ # Add instructions for using the enhanced formula structure
72
+ if hasattr(selected_formula, 'get') and selected_formula.get('subject_line_types') and selected_formula.get('email_structure_template'):
73
+ email_instruction += """
74
+ ENHANCED FORMULA STRUCTURE:
75
+ Use the following structure elements to create more effective emails:
76
+
77
+ SUBJECT LINE TYPES:
78
+ """
79
+ # Add subject line types with descriptions
80
+ for subject_type, description in selected_formula.get('subject_line_types', {}).items():
81
+ email_instruction += f"- {subject_type}: {description}\n"
82
+
83
+ # Add technique types (either infotainment or nurturing)
84
+ technique_key = next((k for k in selected_formula.keys() if k.endswith('_techniques')), None)
85
+ if technique_key:
86
+ email_instruction += f"\n{technique_key.upper()}:\n"
87
+ for technique, description in selected_formula.get(technique_key, {}).items():
88
+ email_instruction += f"- {technique}: {description}\n"
89
+
90
+ # Add email structure template
91
+ email_instruction += "\nEMAIL STRUCTURE TEMPLATE:\n"
92
+ for email_num, structure in selected_formula.get('email_structure_template', {}).items():
93
+ email_instruction += f"\n{email_num.upper()}:\n"
94
+ email_instruction += f"- Purpose: {structure.get('purpose', '')}\n"
95
+ email_instruction += f"- Emotional Focus: {structure.get('emotional_focus', '')}\n"
96
+
97
+ # Add recommended subject types
98
+ if structure.get('recommended_subject_types'):
99
+ email_instruction += "- Recommended Subject Types: " + ", ".join(structure.get('recommended_subject_types', [])) + "\n"
100
+
101
+ # Add recommended techniques
102
+ if structure.get('recommended_techniques'):
103
+ email_instruction += "- Recommended Techniques: " + ", ".join(structure.get('recommended_techniques', [])) + "\n"
104
+
105
+ # Add key elements
106
+ if structure.get('key_elements'):
107
+ email_instruction += "- Key Elements:\n"
108
+ for element in structure.get('key_elements', []):
109
+ email_instruction += f" * {element}\n"
110
+
111
+ # Añadir instrucciones para la idea creativa si existe
112
+ if creative_idea:
113
+ email_instruction += f"""
114
+ CREATIVE CONCEPT:
115
+ Use the following creative concept as the central theme for all emails in the sequence:
116
+ "{creative_idea}"
117
+
118
+ CREATIVE CONCEPT INSTRUCTIONS:
119
+ 1. This concept should be the unifying theme across all emails
120
+ 2. Use it as a metaphor or analogy throughout the sequence
121
+ 3. Develop different aspects of this concept in each email
122
+ 4. Make sure the concept naturally connects to the product benefits
123
+ 5. The concept should make the emails more memorable and engaging
124
+ """
125
+
126
+ # Añadir contenido del archivo si existe
127
+ if file_content:
128
+ email_instruction += f"""
129
+ REFERENCE CONTENT:
130
+ Carefully analyze the following content as a reference for generating emails:
131
+ {file_content[:3000]}
132
+
133
+ ANALYSIS INSTRUCTIONS:
134
+ 1. Extract key information about the product or service mentioned
135
+ 2. Identify the tone, style, and language used
136
+ 3. Detect any data about the target audience or customer avatar
137
+ 4. Look for benefits, features, or pain points mentioned
138
+ 5. Use relevant terms, phrases, or concepts from the content
139
+ 6. Maintain consistency with the brand identity or main message
140
+ 7. Adapt the emails to resonate with the provided content
141
+
142
+ IMPORTANT COMBINATIONS:
143
+ """
144
+ # Updated conditions for specific input combinations
145
+ if product and not target_audience:
146
+ 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.
147
+ """
148
+ elif target_audience and not product:
149
+ 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.
150
+ """
151
+ elif product and target_audience:
152
+ 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.
153
+ """
154
+
155
+ email_instruction += """
156
+ IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
157
+ """
158
+
159
+ # Preparar instrucciones específicas del ángulo una sola vez
160
+ angle_instructions = ""
161
+ if selected_angle != "NINGUNO":
162
+ angle_instructions = f"""
163
+ MAIN ANGLE: {selected_angle}
164
+ SPECIFIC ANGLE INSTRUCTIONS:
165
+ {angles[selected_angle]["instruction"]}
166
+
167
+ IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
168
+ 1. Keep the base structure of the formula intact
169
+ 2. Apply the tone and style of the {selected_angle} angle
170
+ 3. Ensure each element of the formula reflects the angle
171
+ 4. The angle affects "how" it's said, not "what" is said
172
+
173
+ SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
174
+ """
175
+ for example in angles[selected_angle]["examples"]:
176
+ angle_instructions += f"- {example}\n"
177
+
178
+ # Añadir las instrucciones del ángulo al prompt principal
179
+ email_instruction += angle_instructions
180
+
181
+ # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
182
+ email_instruction += (
183
+ f"\nYour task is to create 5 persuasive emails for {target_audience} "
184
+ f"that evoke {emotion} and convince them to {desired_action} about {product}. "
185
+ )
186
+
187
+ # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado
188
+ if angle_instructions:
189
+ email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently."
190
+
191
+ email_instruction += "\n\n"
192
+
193
+ # Take the first 5 examples (or all if less than 5) in order
194
+ sequential_examples = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
195
+
196
+ email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
197
+ for i, example in enumerate(sequential_examples, 1):
198
+ email_instruction += f"{i}. {example}\n"
199
+
200
+ # Añadir instrucciones específicas sobre cómo seguir los ejemplos
201
+ email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
202
+ email_instruction += "1. Maintain the same structure and length as the previous examples\n"
203
+ email_instruction += "2. Use the same tone and writing style\n"
204
+ email_instruction += "3. Replicate the phrase construction patterns\n"
205
+ email_instruction += "4. Preserve the level of specificity and detail\n"
206
+ email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n\n"
207
+
208
+ # Añadir instrucciones específicas sobre la temperatura creativa
209
+ email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n"
210
+
211
+ email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
212
+
213
+ # Consolidar las instrucciones finales en un solo bloque
214
+ final_reminder = ["Follow the structure of the selected formula"]
215
+
216
+ # Add angle-specific instructions only if an angle is selected
217
+ if selected_angle != "NINGUNO":
218
+ final_reminder.extend([
219
+ "Apply the angle as a 'style layer'",
220
+ "Maintain coherence between formula and angle",
221
+ "Ensure each email reflects both elements"
222
+ ])
223
+
224
+ email_instruction += "\nFINAL REMINDER:\n"
225
+ for i, reminder in enumerate(final_reminder, 1):
226
+ email_instruction += f"{i}. {reminder}\n"
227
+
228
+ email_instruction += "\nGENERATE NOW:\nCreate 5 emails that faithfully follow the style and structure of the examples shown.\n"
229
+
230
+ # Modificar la forma de enviar el mensaje según si hay imagen o no
231
+ message_parts = [email_instruction]
232
+
233
+ # Build instruction text more clearly with conditional components
234
+ instruction_components = ["Generate the emails in Spanish following exactly the style and structure of the examples shown."]
235
+
236
+ # Add the image to the message parts if it exists
237
+ if is_image and image_parts:
238
+ message_parts.append(image_parts)
239
+ instruction_components.append("drawing inspiration from the provided image.")
240
+
241
+ # Add final instruction
242
+ instruction_components.append("Do not include explanations, only the emails.")
243
+
244
+ # Join all instruction components with proper spacing
245
+ instruction_text = " ".join(instruction_components)
246
+
247
+ # Create the chat session with the message parts
248
+ try:
249
+ chat_session = model.start_chat(
250
+ history=[
251
+ {
252
+ "role": "user",
253
+ "parts": message_parts,
254
+ },
255
+ ]
256
+ )
257
+
258
+ # Enviar el mensaje con las instrucciones
259
+ response = chat_session.send_message(instruction_text)
260
+
261
+ return response.text
262
+ except genai.types.generation_types.StopCandidateException as e:
263
+ # Handle content filtering/safety issues
264
+ error_message = f"La generación se detuvo debido a restricciones de contenido: {str(e)}"
265
+ st.error(error_message)
266
+ return f"Error: {error_message}"
267
+ except genai.types.generation_types.BlockedPromptException as e:
268
+ # Handle blocked prompt issues
269
+ error_message = f"El prompt fue bloqueado por políticas de contenido: {str(e)}"
270
+ st.error(error_message)
271
+ return f"Error: {error_message}"
272
+ except Exception as e:
273
+ # Handle general API errors
274
+ error_message = f"Error al comunicarse con la API de Google: {str(e)}"
275
+ st.error(error_message)
276
+ return f"Error: {error_message}"
277
+
278
+ # Define the clean_response_text function before it's used
279
+ def clean_response_text(text):
280
+ """Remove extra spaces and normalize whitespace in the response text"""
281
+ # Replace multiple newlines with just two
282
+ text = re.sub(r'\n{3,}', '\n\n', text)
283
+ # Remove leading/trailing whitespace
284
+ text = text.strip()
285
+ return text
286
+
287
+ # Configurar la interfaz de usuario con Streamlit
288
+ st.set_page_config(
289
+ page_title="Email Composer",
290
+ layout="wide",
291
+ menu_items={}, # Empty dict removes the three-dot menu
292
+ initial_sidebar_state="expanded"
293
+ )
294
+
295
+ # Add custom CSS to reduce margins
296
+ st.markdown("""
297
+ <style>
298
+ .main > div {
299
+ padding-top: 1rem;
300
+ }
301
+ </style>
302
+ """, unsafe_allow_html=True)
303
+
304
+ # Leer el contenido del archivo manual.md
305
+ manual_path = "manual.md"
306
+ if os.path.exists(manual_path):
307
+ with open(manual_path, "r", encoding="utf-8") as file:
308
+ manual_content = file.read()
309
+ # Mostrar el contenido del manual en el sidebar
310
+ st.sidebar.markdown(manual_content)
311
+ else:
312
+ st.sidebar.warning("Manual file not found.")
313
+
314
+ # Load CSS from file
315
+ css_path = "styles/main.css"
316
+ if os.path.exists(css_path):
317
+ with open(css_path, "r") as f:
318
+ css = f.read()
319
+ # Apply the CSS
320
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
321
+ else:
322
+ st.warning("CSS file not found.")
323
+
324
+ # Centrar el título y el subtítulo
325
+ st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
326
+ 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)
327
+
328
+ # Crear columnas
329
+ col1, col2 = st.columns([1, 2])
330
+
331
+ # Initialize these variables at the top level before the UI components
332
+ file_content = ""
333
+ is_image = False
334
+ image_parts = None
335
+
336
+ # Columnas de entrada
337
+ with col1:
338
+ target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
339
+ product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
340
+
341
+ # Move "Acción deseada" outside the accordion
342
+ desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
343
+
344
+ # Move formula selection here, right after desired action
345
+ selected_formula_key = st.selectbox(
346
+ "Selecciona una fórmula para tus emails",
347
+ options=list(email_formulas.email_formulas.keys()),
348
+ key="formula_selectbox" # Add a unique key
349
+ )
350
+
351
+ # Only one submit button with a unique key
352
+ submit = st.button("Generar Emails", key="generate_emails_button")
353
+
354
+ # Crear un único acordeón para fórmula, creatividad y ángulo
355
+ with st.expander("Personaliza tus emails"):
356
+ # 1. Idea Creativa al principio con altura de 70px
357
+ 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)
358
+
359
+ # 2. Lo demás (cargador de archivos)
360
+ uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
361
+ type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
362
+
363
+ file_content = ""
364
+ is_image = False
365
+ image_parts = None
366
+
367
+ if uploaded_file is not None:
368
+ # El código para manejar archivos permanece igual
369
+ file_type = uploaded_file.name.split('.')[-1].lower()
370
+
371
+ # Manejar archivos de texto
372
+ if file_type in ['txt', 'pdf', 'docx']:
373
+ # El código para manejar archivos de texto permanece igual
374
+ # ...
375
+ if file_type == 'txt':
376
+ try:
377
+ file_content = uploaded_file.read().decode('utf-8')
378
+ st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
379
+ except Exception as e:
380
+ st.error(f"Error al leer el archivo TXT: {str(e)}")
381
+ file_content = ""
382
+
383
+ elif file_type == 'pdf':
384
+ try:
385
+ import PyPDF2
386
+ pdf_reader = PyPDF2.PdfReader(uploaded_file)
387
+ file_content = ""
388
+ for page in pdf_reader.pages:
389
+ file_content += page.extract_text() + "\n"
390
+ st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
391
+ except Exception as e:
392
+ st.error(f"Error al leer el archivo PDF: {str(e)}")
393
+ file_content = ""
394
+
395
+ elif file_type == 'docx':
396
+ try:
397
+ import docx
398
+ doc = docx.Document(uploaded_file)
399
+ file_content = "\n".join([para.text for para in doc.paragraphs])
400
+ st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
401
+ except Exception as e:
402
+ st.error(f"Error al leer el archivo DOCX: {str(e)}")
403
+ file_content = ""
404
+
405
+ # Manejar archivos de imagen
406
+ elif file_type in ['jpg', 'jpeg', 'png']:
407
+ try:
408
+ from PIL import Image
409
+ image = Image.open(uploaded_file)
410
+ image_bytes = uploaded_file.getvalue()
411
+ image_parts = {
412
+ "mime_type": uploaded_file.type,
413
+ "data": image_bytes
414
+ }
415
+ is_image = True
416
+ st.image(image, caption="Imagen cargada", use_column_width=True)
417
+ except Exception as e:
418
+ st.error(f"Error processing image: {str(e)}")
419
+ is_image = False
420
+
421
+ # 4. Ángulo
422
+ angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
423
+ selected_angle = st.selectbox(
424
+ "Selecciona un ángulo para tus emails",
425
+ options=angle_keys,
426
+ key="angle_selectbox" # Add a unique key
427
+ )
428
+
429
+ # 5. Emoción
430
+ emotion = st.selectbox(
431
+ "¿Qué emoción quieres evocar?",
432
+ options=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
433
+ key="emotion_selectbox" # Add a unique key
434
+ )
435
+
436
+ # 6. Creatividad (slider)
437
+ temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
438
+
439
+ selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
440
+
441
+ # Removed the submit button from here
442
+ # Define a function to validate inputs
443
+ def validate_inputs(file_content, product, target_audience, emotion, desired_action):
444
+ """
445
+ Validates input combinations and returns a tuple of (is_valid, error_message)
446
+ """
447
+ has_file = file_content.strip() != "" if file_content else False
448
+ has_product = product.strip() != ""
449
+ has_audience = target_audience.strip() != ""
450
+ has_emotion = emotion.strip() != "" if emotion else False
451
+ has_action = desired_action.strip() != "" if desired_action else False
452
+
453
+ # Check for valid combinations
454
+ if has_file and has_product:
455
+ return True, "" # File + Product is valid
456
+ elif has_file and has_audience:
457
+ return True, "" # File + Audience is valid
458
+ elif has_product and has_audience and has_emotion and has_action:
459
+ return True, "" # Product + Audience + Emotion + Action is valid
460
+
461
+ # If we get here, no valid combination was found
462
+ if not (has_emotion and has_action):
463
+ return False, "Por favor especifica la emoción que quieres evocar y la acción deseada."
464
+ else:
465
+ 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."
466
+
467
+ # Mostrar los emails generados
468
+ if submit:
469
+ # Validate inputs using the new function
470
+ is_valid, error_message = validate_inputs(
471
+ file_content,
472
+ product,
473
+ target_audience,
474
+ emotion,
475
+ desired_action
476
+ )
477
+
478
+ if is_valid and selected_formula:
479
+ try:
480
+ # Use spinner within col2 context
481
+ with col2:
482
+ with st.spinner("Creando los emails..."):
483
+ # Update the function call to include creative_idea
484
+ generated_emails = generate_emails(
485
+ target_audience,
486
+ product,
487
+ temperature,
488
+ selected_formula,
489
+ selected_angle,
490
+ file_content,
491
+ image_parts,
492
+ is_image,
493
+ emotion,
494
+ desired_action,
495
+ creative_idea # Add the creative idea parameter
496
+ )
497
+
498
+ # Check if the response starts with "Error:"
499
+ if generated_emails.startswith("Error:"):
500
+ st.error(generated_emails)
501
+ else:
502
+ # Clean the response text to remove extra spaces
503
+ generated_emails = clean_response_text(generated_emails)
504
+
505
+ # Get current timestamp for the filename
506
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
507
+
508
+ # Add a download button ABOVE the results
509
+ st.download_button(
510
+ label="DESCARGAR EMAILS",
511
+ data=generated_emails,
512
+ file_name=f"emails_persuasivos_{timestamp}.txt",
513
+ mime="text/plain",
514
+ key="download_top" # Unique key for the top button
515
+ )
516
+
517
+ # Display the generated emails in col2 (still within col2 context)
518
+ st.markdown(f"""
519
+ <div class="results-container">
520
+ <h4>Tus emails persuasivos:</h4>
521
+ <p>{generated_emails}</p>
522
+ </div>
523
+ """, unsafe_allow_html=True)
524
+
525
+ # Download button at the bottom (same as before but with a different key)
526
+ st.download_button(
527
+ label="DESCARGAR EMAILS",
528
+ data=generated_emails,
529
+ file_name=f"emails_persuasivos_{timestamp}.txt",
530
+ mime="text/plain",
531
+ key="download_bottom" # Unique key for the bottom button
532
+ )
533
+ except Exception as e:
534
+ col2.error(f"Error: {str(e)}")
535
+ else:
536
+ if not selected_formula:
537
+ col2.error("Por favor selecciona una fórmula.")
538
+ else:
539
+ col2.error(error_message)