JeCabrera commited on
Commit
a7a382b
·
verified ·
1 Parent(s): 2146d77

Upload 9 files

Browse files
Files changed (4) hide show
  1. README.md +12 -12
  2. app.py +489 -489
  3. formulas/email_formulas.py +2 -4
  4. requirements.txt +9 -9
README.md CHANGED
@@ -1,13 +1,13 @@
1
- ---
2
- sdk: streamlit
3
- colorFrom: yellow
4
- colorTo: gray
5
- pinned: true
6
- title: Email Generator CopyXpert - Backup
7
- license: afl-3.0
8
- emoji: ⚡
9
- thumbnail: >-
10
- https://cdn-uploads.huggingface.co/production/uploads/66c41fa62777c050701989a9/D4k6XvaEzAaSJkNb01JTU.png
11
- short_description: Make emails with AI
12
- sdk_version: 1.44.0
13
  ---
 
1
+ ---
2
+ sdk: streamlit
3
+ colorFrom: yellow
4
+ colorTo: gray
5
+ pinned: true
6
+ title: Email Generator CopyXpert
7
+ license: afl-3.0
8
+ emoji: ⚡
9
+ thumbnail: >-
10
+ https://cdn-uploads.huggingface.co/production/uploads/66c41fa62777c050701989a9/D4k6XvaEzAaSJkNb01JTU.png
11
+ short_description: Make emails with AI
12
+ sdk_version: 1.43.2
13
  ---
app.py CHANGED
@@ -1,489 +1,489 @@
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 with a dividing line
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
- # Agregar 5 ejemplos aleatorios de la fórmula
154
- random_examples = random.sample(selected_formula['examples'], min(5, len(selected_formula['examples'])))
155
-
156
- email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
157
- for i, example in enumerate(random_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
- # Display the generated emails in col2 (still within col2 context)
466
- st.markdown(f"""
467
- <div class="results-container">
468
- <h4>Tus emails persuasivos:</h4>
469
- <p>{generated_emails}</p>
470
- </div>
471
- """, unsafe_allow_html=True)
472
-
473
- # Get current timestamp for the filename
474
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
475
-
476
- # Download button
477
- st.download_button(
478
- label="DESCARGAR EMAILS",
479
- data=generated_emails,
480
- file_name=f"emails_persuasivos_{timestamp}.txt",
481
- mime="text/plain"
482
- )
483
- except Exception as e:
484
- col2.error(f"Error: {str(e)}")
485
- else:
486
- if not selected_formula:
487
- col2.error("Por favor selecciona una fórmula.")
488
- else:
489
- 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
+ # 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
+ # Agregar 5 ejemplos aleatorios de la fórmula
154
+ random_examples = random.sample(selected_formula['examples'], min(5, len(selected_formula['examples'])))
155
+
156
+ email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n"
157
+ for i, example in enumerate(random_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
+ # Display the generated emails in col2 (still within col2 context)
466
+ st.markdown(f"""
467
+ <div class="results-container">
468
+ <h4>Tus emails persuasivos:</h4>
469
+ <p>{generated_emails}</p>
470
+ </div>
471
+ """, unsafe_allow_html=True)
472
+
473
+ # Get current timestamp for the filename
474
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
475
+
476
+ # Download button
477
+ st.download_button(
478
+ label="DESCARGAR EMAILS",
479
+ data=generated_emails,
480
+ file_name=f"emails_persuasivos_{timestamp}.txt",
481
+ mime="text/plain"
482
+ )
483
+ except Exception as e:
484
+ col2.error(f"Error: {str(e)}")
485
+ else:
486
+ if not selected_formula:
487
+ col2.error("Por favor selecciona una fórmula.")
488
+ else:
489
+ col2.error(error_message)
formulas/email_formulas.py CHANGED
@@ -269,9 +269,7 @@ email_formulas = {
269
  """
270
  ASUNTO: 3 estrategias que revolucionarán tu forma de aumentar tus ventas online
271
 
272
- ¿Alguna vez has preguntado por qué algunas personas logran duplicar sus ingresos en línea mientras que otras siguen luchando con conversiones bajas?
273
-
274
- La diferencia no está en el talento innato o la suerte. Está en el conocimiento de ciertas estrategias que la mayoría desconoce.
275
 
276
  Durante los últimos 7 años, he ayudado a cientos de emprendedores digitales a superar exactamente los mismos desafíos que probablemente estás enfrentando ahora:
277
 
@@ -289,7 +287,7 @@ email_formulas = {
289
 
290
  **Estrategia #2: El Principio de Valor Anticipado**
291
 
292
- ¿Sabías que el 83% de emprendedores nunca aprovecha el poder de entregar valor antes de pedir la venta? Este principio simple pero poderoso puede ayudarte a construir confianza instantánea mientras simultáneamente reduces la resistencia a la compra.
293
 
294
  **Estrategia #3: La Técnica de Escalera de Compromiso**
295
 
 
269
  """
270
  ASUNTO: 3 estrategias que revolucionarán tu forma de aumentar tus ventas online
271
 
272
+ La diferencia entre quienes logran duplicar sus ingresos en línea y quienes siguen luchando con conversiones bajas no está en el talento innato o la suerte. Está en el conocimiento de ciertas estrategias que la mayoría desconoce.
 
 
273
 
274
  Durante los últimos 7 años, he ayudado a cientos de emprendedores digitales a superar exactamente los mismos desafíos que probablemente estás enfrentando ahora:
275
 
 
287
 
288
  **Estrategia #2: El Principio de Valor Anticipado**
289
 
290
+ El 83% de emprendedores nunca aprovecha el poder de entregar valor antes de pedir la venta. Este principio simple pero poderoso puede ayudarte a construir confianza instantánea mientras simultáneamente reduces la resistencia a la compra.
291
 
292
  **Estrategia #3: La Técnica de Escalera de Compromiso**
293
 
requirements.txt CHANGED
@@ -1,10 +1,10 @@
1
- streamlit
2
- google-generativeai
3
- python-dotenv
4
- langchain
5
- PyPDF2
6
- chromadb
7
- pdf2image
8
- faiss-cpu
9
- python-docx
10
  pillow
 
1
+ streamlit
2
+ google-generativeai
3
+ python-dotenv
4
+ langchain
5
+ PyPDF2
6
+ chromadb
7
+ pdf2image
8
+ faiss-cpu
9
+ python-docx
10
  pillow