Spaces:
Running
Running
Upload app.py
Browse files
app.py
CHANGED
@@ -1,250 +1,184 @@
|
|
1 |
-
from dotenv import load_dotenv
|
2 |
-
import streamlit as st
|
3 |
-
import os
|
4 |
-
import google.generativeai as genai
|
5 |
-
import
|
6 |
-
from
|
7 |
-
from
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
-
|
66 |
-
|
67 |
-
|
68 |
-
-
|
69 |
-
-
|
70 |
-
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
)
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
""
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
st.markdown("<h1 style='text-align: center;'>Enchanted Hooks</h1>", unsafe_allow_html=True)
|
186 |
-
st.markdown("<h4 style='text-align: center;'>Imagina poder conjurar títulos que no solo informan, sino que encantan. Esta app es tu varita mágica en el mundo del copywriting, transformando cada concepto en un titular cautivador que deja a todos deseando más.</h4>", unsafe_allow_html=True)
|
187 |
-
|
188 |
-
# Crear columnas
|
189 |
-
col1, col2 = st.columns([1, 2])
|
190 |
-
|
191 |
-
# Columnas de entrada
|
192 |
-
with col1:
|
193 |
-
target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios")
|
194 |
-
product = st.text_input("¿Qué producto tienes en mente?", placeholder="Ejemplo: Curso de Inglés")
|
195 |
-
number_of_headlines = st.selectbox("Número de Titulares", options=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], index=4)
|
196 |
-
|
197 |
-
# Crear un único acordeón para fórmula, creatividad y ángulo
|
198 |
-
with st.expander("Personaliza tus titulares"):
|
199 |
-
temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1)
|
200 |
-
|
201 |
-
selected_formula_key = st.selectbox(
|
202 |
-
"Selecciona una fórmula para tus titulares",
|
203 |
-
options=list(headline_formulas.keys())
|
204 |
-
)
|
205 |
-
|
206 |
-
# Automatically use the keys from the angles dictionary
|
207 |
-
# Make sure "NINGUNO" appears first, then the rest alphabetically
|
208 |
-
angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
|
209 |
-
selected_angle = st.selectbox(
|
210 |
-
"Selecciona el ángulo para tus titulares",
|
211 |
-
options=angle_keys
|
212 |
-
)
|
213 |
-
|
214 |
-
selected_formula = headline_formulas[selected_formula_key]
|
215 |
-
|
216 |
-
# Botón de enviar
|
217 |
-
submit = st.button("Generar Titulares")
|
218 |
-
|
219 |
-
# Mostrar los titulares generados
|
220 |
-
if submit:
|
221 |
-
# Check if we have valid inputs
|
222 |
-
has_product = product.strip() != ""
|
223 |
-
has_audience = target_audience.strip() != ""
|
224 |
-
|
225 |
-
# Valid combination: Product + Audience
|
226 |
-
valid_inputs = has_product and has_audience
|
227 |
-
|
228 |
-
if valid_inputs and selected_formula:
|
229 |
-
try:
|
230 |
-
generated_headlines = generate_headlines(
|
231 |
-
number_of_headlines,
|
232 |
-
target_audience,
|
233 |
-
product,
|
234 |
-
temperature,
|
235 |
-
selected_formula,
|
236 |
-
selected_angle
|
237 |
-
)
|
238 |
-
col2.markdown(f"""
|
239 |
-
<div class="results-container">
|
240 |
-
<h4>Observa la magia en acción:</h4>
|
241 |
-
<p>{generated_headlines}</p>
|
242 |
-
</div>
|
243 |
-
""", unsafe_allow_html=True)
|
244 |
-
except ValueError as e:
|
245 |
-
col2.error(f"Error: {str(e)}")
|
246 |
-
else:
|
247 |
-
if not selected_formula:
|
248 |
-
col2.error("Por favor, selecciona una fórmula.")
|
249 |
-
else:
|
250 |
-
col2.error("Por favor, proporciona el público objetivo y el producto.")
|
|
|
1 |
+
from dotenv import load_dotenv
|
2 |
+
import streamlit as st
|
3 |
+
import os
|
4 |
+
import google.generativeai as genai
|
5 |
+
from cta_formulas import cta_formulas
|
6 |
+
from styles import apply_styles
|
7 |
+
from tone_formulas import tone_settings
|
8 |
+
|
9 |
+
# Cargar variables de entorno
|
10 |
+
load_dotenv()
|
11 |
+
|
12 |
+
# Configurar API de Google Gemini
|
13 |
+
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
|
14 |
+
|
15 |
+
def get_gemini_response(product_service, target_audience, desired_action, formula_type, tone_type, temperature, postdata_theme=None, cta_count=5):
|
16 |
+
if not product_service or not target_audience or not desired_action:
|
17 |
+
return "Por favor, completa todos los campos requeridos."
|
18 |
+
|
19 |
+
formula = cta_formulas[formula_type]
|
20 |
+
tone = tone_settings[tone_type]
|
21 |
+
|
22 |
+
# Lista de fórmulas que ya incluyen P.D. en su estructura
|
23 |
+
formulas_with_pd = ["El Último Aviso (No tan Último)", "Cierra con Corazón", "Gancho Cachondo"]
|
24 |
+
|
25 |
+
# Preparar información de postdata
|
26 |
+
postdata_instruction = ""
|
27 |
+
if postdata_theme:
|
28 |
+
# Solo aplicar la instrucción de postdata si la fórmula tiene PD en su estructura
|
29 |
+
if formula_type in formulas_with_pd:
|
30 |
+
postdata_instruction = f"""
|
31 |
+
POSTDATA THEME:
|
32 |
+
Use the following theme for the P.S. section: {postdata_theme}
|
33 |
+
|
34 |
+
IMPORTANT: For formulas with P.S.2 sections, use a DIFFERENT theme or approach than the one used in P.S.1.
|
35 |
+
DO NOT repeat the same urgency factor, benefit, or discount information in both postdatas.
|
36 |
+
|
37 |
+
Examples:
|
38 |
+
- If P.S.1 mentions "only 2 days left", P.S.2 should NOT mention time limits again
|
39 |
+
- If P.S.1 talks about "limited spots", P.S.2 should focus on a different benefit or feature
|
40 |
+
|
41 |
+
Make sure each postdata adds unique value and persuasion elements.
|
42 |
+
"""
|
43 |
+
|
44 |
+
model = genai.GenerativeModel('gemini-2.0-flash')
|
45 |
+
full_prompt = f"""
|
46 |
+
You are an expert copywriter specialized in creating persuasive Calls to Action (CTAs).
|
47 |
+
Analyze (internally, don't include in output) the following information:
|
48 |
+
|
49 |
+
BUSINESS INFORMATION:
|
50 |
+
Product/Service: {product_service}
|
51 |
+
Target Audience: {target_audience}
|
52 |
+
Desired Action: {desired_action}
|
53 |
+
CTA Type: {formula_type}
|
54 |
+
Tone Style: {tone['style']}
|
55 |
+
Keywords to consider: {', '.join(tone['keywords'])}
|
56 |
+
{formula["description"]}
|
57 |
+
{postdata_instruction}
|
58 |
+
|
59 |
+
First, analyze (but don't show) these points:
|
60 |
+
1. TARGET AUDIENCE ANALYSIS:
|
61 |
+
- What motivates them to take action?
|
62 |
+
- What obstacles prevent them from acting?
|
63 |
+
- What immediate benefits are they seeking?
|
64 |
+
- What fears or doubts do they have?
|
65 |
+
- What language and tone resonates with them?
|
66 |
+
|
67 |
+
2. PERSUASION ELEMENTS:
|
68 |
+
- How to make the desired action more appealing?
|
69 |
+
- What emotional triggers will resonate most?
|
70 |
+
- How to create a sense of urgency naturally?
|
71 |
+
- What unique value proposition to emphasize?
|
72 |
+
- How to minimize perceived risk?
|
73 |
+
|
74 |
+
Based on your internal analysis, create {cta_count} different CTAs following EXACTLY the formula structure:
|
75 |
+
{formula["description"]}
|
76 |
+
|
77 |
+
CRITICAL INSTRUCTIONS:
|
78 |
+
- Follow the exact formula structure shown in the description above
|
79 |
+
- Create {cta_count} different CTAs using the same formula pattern
|
80 |
+
- ALL CTAs MUST BE IN SPANISH
|
81 |
+
- DO NOT add postdata (P.S.) to formulas that don't include it in their structure
|
82 |
+
- When a formula includes multiple postdatas (P.S.1 and P.S.2), make sure they focus on DIFFERENT themes and don't repeat the same urgency factors or benefits
|
83 |
+
|
84 |
+
EXAMPLES TO FOLLOW:
|
85 |
+
{formula["examples"]}
|
86 |
+
|
87 |
+
Output EXACTLY in this format based on {formula_type}:
|
88 |
+
1. Follow format from {formula["examples"]}
|
89 |
+
|
90 |
+
2. Follow format from {formula["examples"]}
|
91 |
+
|
92 |
+
3. Follow format from {formula["examples"]}
|
93 |
+
"""
|
94 |
+
|
95 |
+
response = model.generate_content([full_prompt], generation_config={"temperature": temperature})
|
96 |
+
return response.parts[0].text if response and response.parts else "Error al generar contenido."
|
97 |
+
|
98 |
+
# Configurar la aplicación Streamlit
|
99 |
+
st.set_page_config(page_title="CTA Generator", page_icon="🎯", layout="wide")
|
100 |
+
|
101 |
+
# Leer y mostrar el manual en el sidebar
|
102 |
+
with open("manual.md", "r", encoding="utf-8") as file:
|
103 |
+
manual_content = file.read()
|
104 |
+
st.sidebar.markdown(manual_content)
|
105 |
+
|
106 |
+
# Aplicar estilos
|
107 |
+
st.markdown(apply_styles(), unsafe_allow_html=True)
|
108 |
+
|
109 |
+
# Título de la app
|
110 |
+
st.markdown("<h1>Generador de CTAs Persuasivos</h1>", unsafe_allow_html=True)
|
111 |
+
st.markdown("<h3>Crea llamados a la acción que motiven a tu audiencia a dar el siguiente paso.</h3>", unsafe_allow_html=True)
|
112 |
+
|
113 |
+
# Remove the duplicate manual expander from here
|
114 |
+
|
115 |
+
# Crear dos columnas
|
116 |
+
col1, col2 = st.columns([0.4, 0.6]) # 40% for left column, 60% for right column
|
117 |
+
|
118 |
+
# Columna izquierda para inputs
|
119 |
+
with col1:
|
120 |
+
target_audience = st.text_area(
|
121 |
+
"¿Cuál es tu público objetivo?",
|
122 |
+
placeholder="Ejemplo: Emprendedores que buscan automatizar su negocio..."
|
123 |
+
)
|
124 |
+
|
125 |
+
product_service = st.text_area(
|
126 |
+
"¿Cuál es tu producto o servicio?",
|
127 |
+
placeholder="Ejemplo: Curso de automatización con IA, Software de gestión..."
|
128 |
+
)
|
129 |
+
|
130 |
+
desired_action = st.text_area(
|
131 |
+
"¿Qué acción quieres que realicen?",
|
132 |
+
placeholder="Ejemplo: Registrarse al webinar, Descargar la guía gratuita..."
|
133 |
+
)
|
134 |
+
|
135 |
+
# Mover el botón aquí, antes del acordeón
|
136 |
+
generate_button = st.button("Generar CTAs")
|
137 |
+
|
138 |
+
with st.expander("Opciones avanzadas"):
|
139 |
+
formula_type = st.selectbox(
|
140 |
+
"Tipo de CTA:",
|
141 |
+
options=list(cta_formulas.keys())
|
142 |
+
)
|
143 |
+
|
144 |
+
tone_type = st.selectbox(
|
145 |
+
"Tono del CTA:",
|
146 |
+
options=list(tone_settings.keys()),
|
147 |
+
)
|
148 |
+
|
149 |
+
# Nuevos campos para postdata
|
150 |
+
postdata_theme = st.text_input(
|
151 |
+
"Tema o enfoque para la postdata",
|
152 |
+
placeholder="Ejemplo: urgencia, beneficio, descuento"
|
153 |
+
)
|
154 |
+
|
155 |
+
cta_count = st.number_input(
|
156 |
+
"Número de llamados a la acción",
|
157 |
+
min_value=1,
|
158 |
+
max_value=5,
|
159 |
+
value=3
|
160 |
+
)
|
161 |
+
|
162 |
+
temperature = st.slider(
|
163 |
+
"Nivel de creatividad:",
|
164 |
+
min_value=0.0,
|
165 |
+
max_value=2.0,
|
166 |
+
value=1.0,
|
167 |
+
step=0.1,
|
168 |
+
help="Valores más altos generan CTAs más creativos pero menos predecibles."
|
169 |
+
)
|
170 |
+
|
171 |
+
# Columna derecha para resultados
|
172 |
+
with col2:
|
173 |
+
if generate_button and (response := get_gemini_response(
|
174 |
+
product_service,
|
175 |
+
target_audience,
|
176 |
+
desired_action,
|
177 |
+
formula_type,
|
178 |
+
tone_type,
|
179 |
+
temperature,
|
180 |
+
postdata_theme,
|
181 |
+
cta_count
|
182 |
+
)):
|
183 |
+
st.markdown("### Tus Llamados a la Acción")
|
184 |
+
st.write(response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|