JeCabrera commited on
Commit
731c1ef
·
verified ·
1 Parent(s): 4d24e57

Upload 9 files

Browse files
Files changed (1) hide show
  1. app.py +455 -466
app.py CHANGED
@@ -1,466 +1,455 @@
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 # Updated import statement
8
- from angles import angles
9
-
10
- # Cargar las variables de entorno
11
- load_dotenv()
12
-
13
- # Configurar la API de Google
14
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
15
-
16
- # Fórmulas con ejemplos y explicaciones
17
- # email_formulas dictionary has been moved to formulas/email_formulas.py
18
-
19
- # Cambiar el nombre de la función
20
- def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea=""):
21
- # Crear la configuración del modelo
22
- generation_config = {
23
- "temperature": temperature,
24
- "top_p": 0.65,
25
- "top_k": 360,
26
- "max_output_tokens": 8196,
27
- }
28
-
29
- model = genai.GenerativeModel(
30
- model_name="gemini-2.0-flash",
31
- generation_config=generation_config,
32
- )
33
-
34
- # Fixed number of emails to 5
35
- number_of_emails = 5
36
-
37
- # Incluir las instrucciones del sistema en el prompt principal
38
- system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
39
-
40
- You have helped many marketers before me persuade their clients through emotional email sequences.
41
- 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].
42
-
43
- FORMAT RULES:
44
- - Each email must have a clear and attractive subject line
45
- - Include personalized greeting
46
- - The email body must be persuasive and emotional
47
- - Include a clear call to action
48
- - Add a professional signature
49
- - Separate each email with a dividing line
50
-
51
- IMPORTANT ANGLE INSTRUCTIONS:
52
- - The selected angle MUST be applied to EACH email
53
- - The angle modifies HOW the message is expressed, not its structure
54
- - Think of the angle as a "tone overlay" on the content
55
- - The formula provides the structure, the angle provides the style
56
- - Both must work together seamlessly
57
-
58
- FORMAT EXAMPLE:
59
- ---
60
- SUBJECT: [Attractive subject line]
61
-
62
- [Email body with persuasive and emotional content]
63
-
64
- [Clear call to action]
65
-
66
- [Signature]
67
- ---
68
-
69
- IMPORTANT:
70
- - Each email must be unique and memorable
71
- - Avoid clichés and generalities
72
- - Maintain a persuasive but credible tone
73
- - Adapt language to the target audience
74
- - Focus on transformative benefits
75
- - Follow the selected angle style while maintaining the structure"""
76
-
77
- # Add creative idea to system prompt if it exists
78
- if creative_idea:
79
- system_prompt += f"""
80
-
81
- CREATIVE CONCEPT:
82
- Use the following creative concept as the central theme for all emails in the sequence:
83
- "{creative_idea}"
84
-
85
- CREATIVE CONCEPT INSTRUCTIONS:
86
- 1. This concept should be the unifying theme across all emails
87
- 2. Use it as a metaphor or analogy throughout the sequence
88
- 3. Develop different aspects of this concept in each email
89
- 4. Make sure the concept naturally connects to the product benefits
90
- 5. The concept should make the emails more memorable and engaging
91
- """
92
-
93
- # Iniciar el prompt con las instrucciones del sistema
94
- email_instruction = f"{system_prompt}\n\n"
95
-
96
- # Añadir contenido del archivo si existe
97
- if file_content:
98
- email_instruction += f"""
99
- REFERENCE CONTENT:
100
- Carefully analyze the following content as a reference for generating emails:
101
- {file_content[:3000]}
102
-
103
- ANALYSIS INSTRUCTIONS:
104
- 1. Extract key information about the product or service mentioned
105
- 2. Identify the tone, style, and language used
106
- 3. Detect any data about the target audience or customer avatar
107
- 4. Look for benefits, features, or pain points mentioned
108
- 5. Use relevant terms, phrases, or concepts from the content
109
- 6. Maintain consistency with the brand identity or main message
110
- 7. Adapt the emails to resonate with the provided content
111
-
112
- IMPORTANT COMBINATIONS:
113
- """
114
- # Updated conditions for specific input combinations
115
- if product and not target_audience:
116
- 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.
117
- """
118
- elif target_audience and not product:
119
- 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.
120
- """
121
- elif product and target_audience:
122
- 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.
123
- """
124
-
125
- email_instruction += """
126
- IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
127
- """
128
-
129
- # Añadir instrucciones de ángulo solo si no es "NINGUNO"
130
- if selected_angle != "NINGUNO":
131
- email_instruction += f"""
132
- MAIN ANGLE: {selected_angle}
133
- SPECIFIC ANGLE INSTRUCTIONS:
134
- {angles[selected_angle]["instruction"]}
135
-
136
- IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
137
- 1. Keep the base structure of the formula intact
138
- 2. Apply the tone and style of the {selected_angle} angle
139
- 3. Ensure each element of the formula reflects the angle
140
- 4. The angle affects "how" it's said, not "what" is said
141
-
142
- SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
143
- """
144
- for example in angles[selected_angle]["examples"]:
145
- email_instruction += f"- {example}\n"
146
-
147
- # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
148
- email_instruction += (
149
- f"\nYour task is to create {number_of_emails} persuasive emails for {target_audience} "
150
- f"that evoke {emotion} and convince them to {desired_action} about {product}. "
151
- )
152
-
153
- if selected_angle != "NINGUNO":
154
- email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently.\n\n"
155
-
156
- email_instruction += (
157
- f"Avoid obvious mentions of {product} and focus on generating genuine interest"
158
- )
159
-
160
- if selected_angle != "NINGUNO":
161
- email_instruction += f" using the selected angle"
162
-
163
- email_instruction += ".\n\n"
164
-
165
- email_instruction += (
166
- f"IMPORTANT: Carefully study these examples of the selected formula. "
167
- f"Each example represents the style and structure to follow"
168
- )
169
-
170
- if selected_angle != "NINGUNO":
171
- email_instruction += f", adapted to the {selected_angle} angle"
172
-
173
- email_instruction += ":\n\n"
174
-
175
- # En lugar de seleccionar ejemplos aleatorios, tomar los primeros 5 (o todos si hay menos)
176
- examples_to_use = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
177
-
178
- email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
179
- for i, example in enumerate(examples_to_use, 1):
180
- email_instruction += f"{i}. {example}\n"
181
-
182
- email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
183
- email_instruction += "1. Maintain the same structure and length as the previous examples\n"
184
- email_instruction += "2. Use the same tone and writing style\n"
185
- email_instruction += "3. Replicate the phrase construction patterns\n"
186
- email_instruction += "4. Preserve the level of specificity and detail\n"
187
- email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n"
188
- email_instruction += "6. IMPORTANT: Each of your 5 emails should directly follow the structure and logic of the corresponding example\n"
189
- email_instruction += " - Email 1 should follow Example 1's structure and logic\n"
190
- email_instruction += " - Email 2 should follow Example 2's structure and logic\n"
191
- email_instruction += " - And so on for all 5 emails\n\n"
192
-
193
- email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
194
-
195
- # CORRECTO (con indentación):
196
- if selected_angle != "NINGUNO":
197
- email_instruction += f"""
198
- FINAL REMINDER:
199
- 1. Follow the structure of the selected formula
200
- 2. Apply the angle as a "style layer"
201
- 3. Maintain coherence between formula and angle
202
- 4. Ensure each email reflects both elements
203
-
204
- GENERATE NOW:
205
- Create {number_of_emails} emails that faithfully follow the style and structure of the examples shown.
206
- """
207
- else:
208
- email_instruction += f"""
209
- GENERATE NOW:
210
- Create {number_of_emails} emails that faithfully follow the style and structure of the examples shown.
211
- """
212
-
213
- # Modificar la forma de enviar el mensaje según si hay imagen o no
214
- message_parts = [email_instruction]
215
-
216
- # Add the image to the message parts if it exists
217
- if is_image and image_parts:
218
- message_parts.append(image_parts)
219
- instruction_text = "Generate the emails in Spanish following exactly the style of the examples shown, drawing inspiration from the provided image."
220
- else:
221
- instruction_text = "Generate the emails in Spanish following exactly the style of the examples shown."
222
-
223
- # Common instruction for both cases
224
- instruction_text += " Do not include explanations, only the emails. IMPORTANT: Do not include greetings like 'Hello [Name]' and make sure that the postscripts (P.D.) are smaller and more discrete than the main body of the email, using a lighter format."
225
-
226
- # Create the chat session with the message parts
227
- chat_session = model.start_chat(
228
- history=[
229
- {
230
- "role": "user",
231
- "parts": message_parts,
232
- },
233
- ]
234
- )
235
-
236
- # Enviar el mensaje con las instrucciones
237
- response = chat_session.send_message(instruction_text)
238
-
239
- return response.text
240
-
241
- # Define the clean_response_text function before it's used
242
- def clean_response_text(text):
243
- """Remove extra spaces and normalize whitespace in the response text"""
244
- import re
245
- # Replace multiple newlines with just two
246
- text = re.sub(r'\n{3,}', '\n\n', text)
247
- # Remove leading/trailing whitespace
248
- text = text.strip()
249
- return text
250
-
251
- # Configurar la interfaz de usuario con Streamlit
252
- st.set_page_config(
253
- page_title="Email Composer",
254
- layout="wide",
255
- menu_items={}, # Empty dict removes the three-dot menu
256
- initial_sidebar_state="expanded"
257
- )
258
-
259
- # Add custom CSS to reduce margins
260
- st.markdown("""
261
- <style>
262
- .main > div {
263
- padding-top: 1rem;
264
- }
265
- </style>
266
- """, unsafe_allow_html=True)
267
-
268
- # Leer el contenido del archivo manual.md
269
- with open("manual.md", "r", encoding="utf-8") as file:
270
- manual_content = file.read()
271
-
272
- # Mostrar el contenido del manual en el sidebar
273
- st.sidebar.markdown(manual_content)
274
-
275
- # Load CSS from file
276
- with open("styles/main.css", "r") as f:
277
- css = f.read()
278
-
279
- # Apply the CSS
280
- st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
281
-
282
- # Centrar el título y el subtítulo
283
- st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
284
- 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)
285
-
286
- # Crear columnas
287
- col1, col2 = st.columns([1, 2])
288
-
289
- # Columnas de entrada
290
- with col1:
291
- target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
292
- product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
293
-
294
- # Move "Acción deseada" outside the accordion
295
- desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
296
-
297
- # Move formula selection here, right after desired action
298
- selected_formula_key = st.selectbox(
299
- "Selecciona una fórmula para tus emails",
300
- options=list(email_formulas.email_formulas.keys()),
301
- key="formula_selectbox" # Add a unique key
302
- )
303
-
304
- # Only one submit button with a unique key
305
- submit = st.button("Generar Emails", key="generate_emails_button")
306
-
307
- # Crear un único acordeón para fórmula, creatividad y ángulo
308
- with st.expander("Personaliza tus emails"):
309
- # 1. Idea Creativa al principio con altura de 70px
310
- 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)
311
-
312
- # 2. Lo demás (cargador de archivos)
313
- uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
314
- type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
315
-
316
- file_content = ""
317
- is_image = False
318
- image_parts = None
319
-
320
- if uploaded_file is not None:
321
- # El código para manejar archivos permanece igual
322
- file_type = uploaded_file.name.split('.')[-1].lower()
323
-
324
- # Manejar archivos de texto
325
- if file_type in ['txt', 'pdf', 'docx']:
326
- # El código para manejar archivos de texto permanece igual
327
- # ...
328
- if file_type == 'txt':
329
- try:
330
- file_content = uploaded_file.read().decode('utf-8')
331
- st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
332
- except Exception as e:
333
- st.error(f"Error al leer el archivo TXT: {str(e)}")
334
- file_content = ""
335
-
336
- elif file_type == 'pdf':
337
- try:
338
- import PyPDF2
339
- pdf_reader = PyPDF2.PdfReader(uploaded_file)
340
- file_content = ""
341
- for page in pdf_reader.pages:
342
- file_content += page.extract_text() + "\n"
343
- st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
344
- except Exception as e:
345
- st.error(f"Error al leer el archivo PDF: {str(e)}")
346
- file_content = ""
347
-
348
- elif file_type == 'docx':
349
- try:
350
- import docx
351
- doc = docx.Document(uploaded_file)
352
- file_content = "\n".join([para.text for para in doc.paragraphs])
353
- st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
354
- except Exception as e:
355
- st.error(f"Error al leer el archivo DOCX: {str(e)}")
356
- file_content = ""
357
-
358
- # Manejar archivos de imagen
359
- elif file_type in ['jpg', 'jpeg', 'png']:
360
- try:
361
- from PIL import Image
362
- image = Image.open(uploaded_file)
363
- image_bytes = uploaded_file.getvalue()
364
- image_parts = {
365
- "mime_type": uploaded_file.type,
366
- "data": image_bytes
367
- }
368
- is_image = True
369
- st.image(image, caption="Imagen cargada", use_column_width=True)
370
- except Exception as e:
371
- st.error(f"Error processing image: {str(e)}")
372
- is_image = False
373
-
374
- # 4. Ángulo
375
- angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
376
- selected_angle = st.selectbox(
377
- "Selecciona un ángulo para tus emails",
378
- options=angle_keys,
379
- key="angle_selectbox" # Add a unique key
380
- )
381
-
382
- # 5. Emoción
383
- emotion = st.selectbox(
384
- "¿Qué emoción quieres evocar?",
385
- options=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
386
- key="emotion_selectbox" # Add a unique key
387
- )
388
-
389
- # 6. Creatividad (slider)
390
- temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
391
-
392
- selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
393
-
394
- # Removed the submit button from here
395
- # Mostrar los emails generados
396
- if submit:
397
- # Check if we have a valid combination of inputs
398
- has_file = 'file_content' in locals() and file_content.strip() != ""
399
- has_product = product.strip() != ""
400
- has_audience = target_audience.strip() != ""
401
- has_emotion = 'emotion' in locals() and emotion.strip() != ""
402
- has_action = 'desired_action' in locals() and desired_action.strip() != ""
403
-
404
- # Valid combinations:
405
- # 1. File + Product (no audience needed)
406
- # 2. File + Audience (no product needed)
407
- # 3. Product + Audience (traditional way)
408
- valid_inputs = (
409
- (has_file and has_product) or
410
- (has_file and has_audience) or
411
- (has_product and has_audience and has_emotion and has_action)
412
- )
413
-
414
- if valid_inputs and selected_formula:
415
- try:
416
- # Use spinner within col2 context
417
- with col2:
418
- with st.spinner("Creando los emails..."):
419
- # Update the function call to include creative_idea
420
- generated_emails = generate_emails(
421
- target_audience,
422
- product,
423
- temperature,
424
- selected_formula,
425
- selected_angle,
426
- file_content if 'file_content' in locals() else "",
427
- image_parts if 'image_parts' in locals() else None,
428
- is_image if 'is_image' in locals() else False,
429
- emotion,
430
- desired_action,
431
- creative_idea # Add the creative idea parameter
432
- )
433
-
434
- # Clean the response text to remove extra spaces
435
- generated_emails = clean_response_text(generated_emails)
436
-
437
- # Display the generated emails in col2 (still within col2 context)
438
- st.markdown(f"""
439
- <div class="results-container">
440
- <h4>Tus emails persuasivos:</h4>
441
- <p>{generated_emails}</p>
442
- </div>
443
- """, unsafe_allow_html=True)
444
-
445
- # Add download button (styling is in styles/main.css)
446
- import datetime
447
-
448
- # Get current timestamp for the filename
449
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
450
-
451
- # Download button
452
- st.download_button(
453
- label="DESCARGAR EMAILS",
454
- data=generated_emails,
455
- file_name=f"emails_persuasivos_{timestamp}.txt",
456
- mime="text/plain"
457
- )
458
- except ValueError as e:
459
- col2.error(f"Error: {str(e)}")
460
- else:
461
- if not selected_formula:
462
- col2.error("Por favor selecciona una fórmula.")
463
- elif not (has_emotion and has_action):
464
- col2.error("Por favor especifica la emoción que quieres evocar y la acción deseada.")
465
- else:
466
- col2.error("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.")
 
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 # Updated import statement
8
+ from angles import angles
9
+
10
+ # Cargar las variables de entorno
11
+ load_dotenv()
12
+
13
+ # Configurar la API de Google
14
+ genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
15
+
16
+ # Fórmulas con ejemplos y explicaciones
17
+ # email_formulas dictionary has been moved to formulas/email_formulas.py
18
+
19
+ # Cambiar el nombre de la función
20
+ def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea=""):
21
+ # Crear la configuración del modelo
22
+ generation_config = {
23
+ "temperature": temperature,
24
+ "top_p": 0.65,
25
+ "top_k": 360,
26
+ "max_output_tokens": 8196,
27
+ }
28
+
29
+ model = genai.GenerativeModel(
30
+ model_name="gemini-2.0-flash",
31
+ generation_config=generation_config,
32
+ )
33
+
34
+ # Definir las instrucciones de formato una sola vez
35
+ format_instructions = """
36
+ FORMAT RULES:
37
+ - Each email must have a clear and attractive subject line
38
+ - The email body must be persuasive and emotional
39
+ - Include a clear call to action
40
+ - Add a professional signature
41
+ - Separate each email with a dividing line
42
+ - Do not include greetings like 'Hello [Name]'
43
+ - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body
44
+ """
45
+
46
+ # Incluir las instrucciones del sistema en el prompt principal
47
+ system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy.
48
+
49
+ You have helped many marketers before me persuade their clients through emotional email sequences.
50
+ 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].
51
+
52
+ {format_instructions}
53
+
54
+ IMPORTANT:
55
+ - Each email must be unique and memorable
56
+ - Avoid clichés and generalities
57
+ - Maintain a persuasive but credible tone
58
+ - Adapt language to the target audience
59
+ - Focus on transformative benefits"""
60
+
61
+ # Iniciar el prompt con las instrucciones del sistema
62
+ email_instruction = f"{system_prompt}\n\n"
63
+
64
+ # Añadir instrucciones para la idea creativa si existe
65
+ if creative_idea:
66
+ email_instruction += f"""
67
+ CREATIVE CONCEPT:
68
+ Use the following creative concept as the central theme for all emails in the sequence:
69
+ "{creative_idea}"
70
+
71
+ CREATIVE CONCEPT INSTRUCTIONS:
72
+ 1. This concept should be the unifying theme across all emails
73
+ 2. Use it as a metaphor or analogy throughout the sequence
74
+ 3. Develop different aspects of this concept in each email
75
+ 4. Make sure the concept naturally connects to the product benefits
76
+ 5. The concept should make the emails more memorable and engaging
77
+ """
78
+
79
+ # Añadir contenido del archivo si existe
80
+ if file_content:
81
+ email_instruction += f"""
82
+ REFERENCE CONTENT:
83
+ Carefully analyze the following content as a reference for generating emails:
84
+ {file_content[:3000]}
85
+
86
+ ANALYSIS INSTRUCTIONS:
87
+ 1. Extract key information about the product or service mentioned
88
+ 2. Identify the tone, style, and language used
89
+ 3. Detect any data about the target audience or customer avatar
90
+ 4. Look for benefits, features, or pain points mentioned
91
+ 5. Use relevant terms, phrases, or concepts from the content
92
+ 6. Maintain consistency with the brand identity or main message
93
+ 7. Adapt the emails to resonate with the provided content
94
+
95
+ IMPORTANT COMBINATIONS:
96
+ """
97
+ # Updated conditions for specific input combinations
98
+ if product and not target_audience:
99
+ 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.
100
+ """
101
+ elif target_audience and not product:
102
+ 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.
103
+ """
104
+ elif product and target_audience:
105
+ 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.
106
+ """
107
+
108
+ email_instruction += """
109
+ IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle.
110
+ """
111
+
112
+ # Preparar instrucciones específicas del ángulo una sola vez
113
+ angle_instructions = ""
114
+ if selected_angle != "NINGUNO":
115
+ angle_instructions = f"""
116
+ MAIN ANGLE: {selected_angle}
117
+ SPECIFIC ANGLE INSTRUCTIONS:
118
+ {angles[selected_angle]["instruction"]}
119
+
120
+ IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure:
121
+ 1. Keep the base structure of the formula intact
122
+ 2. Apply the tone and style of the {selected_angle} angle
123
+ 3. Ensure each element of the formula reflects the angle
124
+ 4. The angle affects "how" it's said, not "what" is said
125
+
126
+ SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE:
127
+ """
128
+ for example in angles[selected_angle]["examples"]:
129
+ angle_instructions += f"- {example}\n"
130
+
131
+ # Añadir las instrucciones del ángulo al prompt principal
132
+ email_instruction += angle_instructions
133
+
134
+ # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada
135
+ email_instruction += (
136
+ f"\nYour task is to create 5 persuasive emails for {target_audience} "
137
+ f"that evoke {emotion} and convince them to {desired_action} about {product}. "
138
+ )
139
+
140
+ # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado
141
+ if angle_instructions:
142
+ email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently.\n\n"
143
+
144
+ email_instruction += (
145
+ f"Avoid obvious mentions of {product} and focus on generating genuine interest"
146
+ )
147
+
148
+ if angle_instructions:
149
+ email_instruction += f" using the selected angle"
150
+
151
+ email_instruction += ".\n\n"
152
+
153
+ # Agregar ejemplos de la fórmula
154
+ examples_to_use = selected_formula['examples'][:min(5, len(selected_formula['examples']))]
155
+
156
+ email_instruction += "FORMULA EXAMPLES TO STUDY (DO NOT COPY DIRECTLY):\n"
157
+ for i, example in enumerate(examples_to_use, 1):
158
+ email_instruction += f"{i}. {example}\n"
159
+
160
+ # Consolidar todas las instrucciones específicas en un solo bloque
161
+ specific_instructions = [
162
+ "DO NOT copy the examples directly - create fresh, original content",
163
+ f"Follow the logical STRUCTURE and SEQUENCE of the formula for {target_audience} and {product}",
164
+ "Maintain the same persuasive techniques and psychological triggers",
165
+ "Create a cohesive sequence where emails reference each other naturally",
166
+ "Use the examples only as inspiration for the underlying structure and flow",
167
+ "If an email references a previous one (like 'As I mentioned yesterday'), ensure this is logically consistent",
168
+ "Be creative with stories, analogies, and examples while maintaining the formula's effectiveness"
169
+ ]
170
+
171
+ email_instruction += "\nSPECIFIC INSTRUCTIONS:\n"
172
+ for i, instruction in enumerate(specific_instructions, 1):
173
+ email_instruction += f"{i}. {instruction}\n"
174
+
175
+ # Añadir instrucciones específicas sobre la temperatura creativa
176
+ email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n"
177
+
178
+ email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n"
179
+
180
+ # Consolidar las instrucciones finales en un solo bloque
181
+ final_instructions = [
182
+ "Follow the logical structure but not the exact wording",
183
+ "Be creative and original with your content",
184
+ "Maintain the sequence flow between emails"
185
+ ]
186
+
187
+ # Añadir instrucciones específicas para el ángulo si es necesario
188
+ if selected_angle != "NINGUNO":
189
+ final_instructions.extend([
190
+ "Apply the angle as a 'style layer'",
191
+ "Maintain coherence between formula and angle",
192
+ "Ensure each email reflects both elements"
193
+ ])
194
+
195
+ email_instruction += "\nFINAL REMINDER:\n"
196
+ for i, instruction in enumerate(final_instructions, 1):
197
+ email_instruction += f"{i}. {instruction}\n"
198
+
199
+ # Simplify the final instruction to clearly specify 5 emails
200
+ email_instruction += f"\nGENERATE NOW:\nCreate 5 CREATIVE emails that follow the logical structure but with original content.\n"
201
+
202
+ # Modificar la forma de enviar el mensaje según si hay imagen o no
203
+ message_parts = [email_instruction]
204
+
205
+ # Add the image to the message parts if it exists
206
+ if is_image and image_parts:
207
+ message_parts.append(image_parts)
208
+ instruction_text = "Generate the emails in Spanish following exactly the style of the examples shown, drawing inspiration from the provided image."
209
+ else:
210
+ instruction_text = "Generate the emails in Spanish following exactly the style of the examples shown."
211
+
212
+ # Simplificar las instrucciones finales para evitar redundancia
213
+ instruction_text += " Do not include explanations, only the emails. IMPORTANT: Do not include greetings like 'Hello [Name]' and make sure that the postscripts (P.D.) are smaller and more discrete than the main body of the email, using a lighter format."
214
+
215
+ # Create the chat session with the message parts
216
+ chat_session = model.start_chat(
217
+ history=[
218
+ {
219
+ "role": "user",
220
+ "parts": message_parts,
221
+ },
222
+ ]
223
+ )
224
+
225
+ # Enviar el mensaje con las instrucciones
226
+ response = chat_session.send_message(instruction_text)
227
+
228
+ return response.text
229
+
230
+ # Define the clean_response_text function before it's used
231
+ def clean_response_text(text):
232
+ """Remove extra spaces and normalize whitespace in the response text"""
233
+ import re
234
+ # Replace multiple newlines with just two
235
+ text = re.sub(r'\n{3,}', '\n\n', text)
236
+ # Remove leading/trailing whitespace
237
+ text = text.strip()
238
+ return text
239
+
240
+ # Configurar la interfaz de usuario con Streamlit
241
+ st.set_page_config(
242
+ page_title="Email Composer",
243
+ layout="wide",
244
+ menu_items={}, # Empty dict removes the three-dot menu
245
+ initial_sidebar_state="expanded"
246
+ )
247
+
248
+ # Add custom CSS to reduce margins
249
+ st.markdown("""
250
+ <style>
251
+ .main > div {
252
+ padding-top: 1rem;
253
+ }
254
+ </style>
255
+ """, unsafe_allow_html=True)
256
+
257
+ # Leer el contenido del archivo manual.md
258
+ with open("manual.md", "r", encoding="utf-8") as file:
259
+ manual_content = file.read()
260
+
261
+ # Mostrar el contenido del manual en el sidebar
262
+ st.sidebar.markdown(manual_content)
263
+
264
+ # Load CSS from file
265
+ with open("styles/main.css", "r") as f:
266
+ css = f.read()
267
+
268
+ # Apply the CSS
269
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
270
+
271
+ # Centrar el título y el subtítulo
272
+ st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True)
273
+ 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)
274
+
275
+ # Crear columnas
276
+ col1, col2 = st.columns([1, 2])
277
+
278
+ # Columnas de entrada
279
+ with col1:
280
+ target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
281
+ product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés")
282
+
283
+ # Move "Acción deseada" outside the accordion
284
+ desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita")
285
+
286
+ # Move formula selection here, right after desired action
287
+ selected_formula_key = st.selectbox(
288
+ "Selecciona una fórmula para tus emails",
289
+ options=list(email_formulas.email_formulas.keys()),
290
+ key="formula_selectbox" # Add a unique key
291
+ )
292
+
293
+ # Only one submit button with a unique key
294
+ submit = st.button("Generar Emails", key="generate_emails_button")
295
+
296
+ # Crear un único acordeón para fórmula, creatividad y ángulo
297
+ with st.expander("Personaliza tus emails"):
298
+ # 1. Idea Creativa al principio con altura de 70px
299
+ 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)
300
+
301
+ # 2. Lo demás (cargador de archivos)
302
+ uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia",
303
+ type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png'])
304
+
305
+ file_content = ""
306
+ is_image = False
307
+ image_parts = None
308
+
309
+ if uploaded_file is not None:
310
+ # El código para manejar archivos permanece igual
311
+ file_type = uploaded_file.name.split('.')[-1].lower()
312
+
313
+ # Manejar archivos de texto
314
+ if file_type in ['txt', 'pdf', 'docx']:
315
+ # El código para manejar archivos de texto permanece igual
316
+ # ...
317
+ if file_type == 'txt':
318
+ try:
319
+ file_content = uploaded_file.read().decode('utf-8')
320
+ st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}")
321
+ except Exception as e:
322
+ st.error(f"Error al leer el archivo TXT: {str(e)}")
323
+ file_content = ""
324
+
325
+ elif file_type == 'pdf':
326
+ try:
327
+ import PyPDF2
328
+ pdf_reader = PyPDF2.PdfReader(uploaded_file)
329
+ file_content = ""
330
+ for page in pdf_reader.pages:
331
+ file_content += page.extract_text() + "\n"
332
+ st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}")
333
+ except Exception as e:
334
+ st.error(f"Error al leer el archivo PDF: {str(e)}")
335
+ file_content = ""
336
+
337
+ elif file_type == 'docx':
338
+ try:
339
+ import docx
340
+ doc = docx.Document(uploaded_file)
341
+ file_content = "\n".join([para.text for para in doc.paragraphs])
342
+ st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}")
343
+ except Exception as e:
344
+ st.error(f"Error al leer el archivo DOCX: {str(e)}")
345
+ file_content = ""
346
+
347
+ # Manejar archivos de imagen
348
+ elif file_type in ['jpg', 'jpeg', 'png']:
349
+ try:
350
+ from PIL import Image
351
+ image = Image.open(uploaded_file)
352
+ image_bytes = uploaded_file.getvalue()
353
+ image_parts = {
354
+ "mime_type": uploaded_file.type,
355
+ "data": image_bytes
356
+ }
357
+ is_image = True
358
+ st.image(image, caption="Imagen cargada", use_column_width=True)
359
+ except Exception as e:
360
+ st.error(f"Error processing image: {str(e)}")
361
+ is_image = False
362
+
363
+ # 4. Ángulo
364
+ angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
365
+ selected_angle = st.selectbox(
366
+ "Selecciona un ángulo para tus emails",
367
+ options=angle_keys,
368
+ key="angle_selectbox" # Add a unique key
369
+ )
370
+
371
+ # 5. Emoción
372
+ emotion = st.selectbox(
373
+ "¿Qué emoción quieres evocar?",
374
+ options=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
375
+ key="emotion_selectbox" # Add a unique key
376
+ )
377
+
378
+ # 6. Creatividad (slider)
379
+ temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
380
+
381
+ selected_formula = email_formulas.email_formulas[selected_formula_key] # Updated reference
382
+
383
+ # Removed the submit button from here
384
+ # Mostrar los emails generados
385
+ if submit:
386
+ # Check if we have a valid combination of inputs
387
+ has_file = 'file_content' in locals() and file_content.strip() != ""
388
+ has_product = product.strip() != ""
389
+ has_audience = target_audience.strip() != ""
390
+ has_emotion = 'emotion' in locals() and emotion.strip() != ""
391
+ has_action = 'desired_action' in locals() and desired_action.strip() != ""
392
+
393
+ # Valid combinations:
394
+ # 1. File + Product (no audience needed)
395
+ # 2. File + Audience (no product needed)
396
+ # 3. Product + Audience (traditional way)
397
+ valid_inputs = (
398
+ (has_file and has_product) or
399
+ (has_file and has_audience) or
400
+ (has_product and has_audience and has_emotion and has_action)
401
+ )
402
+
403
+ if valid_inputs and selected_formula:
404
+ try:
405
+ # Use spinner within col2 context
406
+ with col2:
407
+ with st.spinner("Creando los emails..."):
408
+ # Update the function call to include creative_idea
409
+ generated_emails = generate_emails(
410
+ target_audience,
411
+ product,
412
+ temperature,
413
+ selected_formula,
414
+ selected_angle,
415
+ file_content if 'file_content' in locals() else "",
416
+ image_parts if 'image_parts' in locals() else None,
417
+ is_image if 'is_image' in locals() else False,
418
+ emotion,
419
+ desired_action,
420
+ creative_idea # Add the creative idea parameter
421
+ )
422
+
423
+ # Clean the response text to remove extra spaces
424
+ generated_emails = clean_response_text(generated_emails)
425
+
426
+ # Display the generated emails in col2 (still within col2 context)
427
+ st.markdown(f"""
428
+ <div class="results-container">
429
+ <h4>Tus emails persuasivos:</h4>
430
+ <p>{generated_emails}</p>
431
+ </div>
432
+ """, unsafe_allow_html=True)
433
+
434
+ # Add download button (styling is in styles/main.css)
435
+ import datetime
436
+
437
+ # Get current timestamp for the filename
438
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
439
+
440
+ # Download button
441
+ st.download_button(
442
+ label="DESCARGAR EMAILS",
443
+ data=generated_emails,
444
+ file_name=f"emails_persuasivos_{timestamp}.txt",
445
+ mime="text/plain"
446
+ )
447
+ except ValueError as e:
448
+ col2.error(f"Error: {str(e)}")
449
+ else:
450
+ if not selected_formula:
451
+ col2.error("Por favor selecciona una fórmula.")
452
+ elif not (has_emotion and has_action):
453
+ col2.error("Por favor especifica la emoción que quieres evocar y la acción deseada.")
454
+ else:
455
+ col2.error("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.")