JeCabrera commited on
Commit
fe32cde
·
verified ·
1 Parent(s): 5eb9919

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -340
app.py CHANGED
@@ -4,65 +4,21 @@ import os
4
  from google import genai
5
  from google.genai import types
6
  import random
7
- import re
8
-
9
  from formulas import headline_formulas
10
  from angles import angles
11
 
12
-
13
- # =============================
14
- # CONFIGURACIÓN
15
- # =============================
16
-
17
  load_dotenv()
18
 
19
- client = genai.Client(
20
- api_key=os.getenv("GOOGLE_API_KEY")
21
- )
22
-
23
-
24
- # =============================
25
- # NORMALIZAR TITULARES
26
- # =============================
27
-
28
- def normalize_headlines(text):
29
- """
30
- Asegura que cada titular se muestre correctamente,
31
- aunque el modelo devuelva espacios inconsistentes.
32
- """
33
-
34
- items = re.split(r'\s*(?=\d+\.\s)', text)
35
-
36
- cleaned = []
37
-
38
- for item in items:
39
- item = item.strip()
40
-
41
- if item:
42
- cleaned.append(item)
43
-
44
- return "<br>".join(cleaned)
45
 
46
 
47
- # =============================
48
- # CONTEXTO INTELIGENTE
49
- # =============================
50
-
51
- def build_headline_context(
52
- selected_formula_key,
53
- selected_angle,
54
- target_audience,
55
- product
56
- ):
57
  selected_formula = headline_formulas[selected_formula_key]
58
 
59
- formula_description = selected_formula.get(
60
- "description", ""
61
- ).strip()
62
-
63
- formula_examples = selected_formula.get(
64
- "examples", []
65
- )
66
 
67
  random_examples = random.sample(
68
  formula_examples,
@@ -72,415 +28,257 @@ def build_headline_context(
72
  angle_instruction = ""
73
  angle_examples = []
74
 
75
- if (
76
- selected_angle != "NINGUNO"
77
- and selected_angle in angles
78
- ):
79
- angle_instruction = angles[selected_angle].get(
80
- "instruction", ""
81
- ).strip()
82
-
83
- angle_examples = angles[selected_angle].get(
84
- "examples", []
85
- )[:3]
86
 
87
  extra_guidance = [
88
-
89
- f"Escribe para {target_audience} como personas reales.",
90
-
91
- f"Piensa en qué desea ese público y qué objeciones tiene sobre {product}.",
92
-
93
- "No copies los ejemplos.",
94
-
95
- "Haz que cada titular sea claro y creíble.",
96
-
97
- "Genera curiosidad sin depender del nombre del producto."
98
-
99
  ]
100
 
101
  return {
102
-
103
- "formula_description":
104
- formula_description,
105
-
106
- "formula_examples":
107
- random_examples,
108
-
109
- "angle_instruction":
110
- angle_instruction,
111
-
112
- "angle_examples":
113
- angle_examples,
114
-
115
- "extra_guidance":
116
- extra_guidance
117
  }
118
 
119
 
120
- # =============================
121
- # GENERAR TITULARES
122
- # =============================
123
-
124
- def generate_headlines(
125
- number_of_headlines,
126
- target_audience,
127
- product,
128
- temperature,
129
- selected_formula_key,
130
- selected_formula,
131
- selected_angle
132
- ):
133
-
134
  context = build_headline_context(
135
- selected_formula_key,
136
- selected_angle,
137
- target_audience,
138
- product
139
  )
140
 
141
- system_prompt = """
142
-
143
- You are a world-class copywriter.
144
 
145
  FORMAT RULES:
146
-
147
  - Each headline must start with number and period
148
  - One headline per line
149
- - No explanations
150
- - Avoid unnecessary symbols
151
- - Each headline must be complete
152
- - Maintain curiosity and clarity
153
-
 
 
 
 
 
 
154
  """
155
 
156
  headlines_instruction = f"{system_prompt}\n\n"
157
 
158
  if selected_angle != "NINGUNO":
159
-
160
  headlines_instruction += f"""
 
 
161
 
162
- ÁNGULO PRINCIPAL:
163
-
164
- {selected_angle}
165
-
166
- INSTRUCCIONES:
167
-
168
  {context["angle_instruction"]}
169
 
 
170
  """
171
-
172
  for example in context["angle_examples"]:
173
- headlines_instruction += (
174
- f"- {example}\n"
175
- )
176
 
177
  headlines_instruction += (
178
-
179
- f"\nCrea {number_of_headlines} titulares "
180
-
181
- f"para {target_audience} "
182
-
183
- f"sobre {product}.\n\n"
184
-
185
  )
186
 
187
  headlines_instruction += (
188
- "Antes de escribir, entiende al público.\n\n"
 
 
189
  )
190
 
191
- headlines_instruction += "GUÍA ADICIONAL:\n"
192
-
193
- for rule in context["extra_guidance"]:
194
  headlines_instruction += (
195
- f"- {rule}\n"
196
  )
197
 
198
- headlines_instruction += "\n"
199
-
200
  headlines_instruction += (
201
- "EJEMPLOS DE LA FÓRMULA:\n"
202
  )
203
 
204
- for i, example in enumerate(
205
- context["formula_examples"],
206
- 1
207
- ):
208
- headlines_instruction += (
209
- f"{i}. {example}\n"
210
- )
211
 
 
 
 
 
 
212
  headlines_instruction += "\n"
213
 
214
  headlines_instruction += (
215
- f"FÓRMULA:\n"
216
- f"{context['formula_description']}\n\n"
217
  )
218
 
219
- response = client.models.generate_content(
 
220
 
221
- model="gemini-2.5-flash-lite",
222
 
223
- contents=headlines_instruction,
224
 
225
- config=types.GenerateContentConfig(
 
 
226
 
227
- temperature=temperature,
 
 
 
 
 
228
 
229
- top_p=0.65,
230
 
231
- max_output_tokens=8196,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
 
 
 
 
 
 
 
 
233
  ),
234
  )
235
 
236
  return response.text
237
 
238
 
239
- # =============================
240
- # INTERFAZ STREAMLIT
241
- # =============================
242
-
243
- st.set_page_config(
244
- page_title="Enchanted Hooks",
245
- layout="wide"
246
- )
247
-
248
-
249
- # Sidebar manual
250
-
251
- with open(
252
- "manual.md",
253
- "r",
254
- encoding="utf-8"
255
- ) as file:
256
 
 
 
257
  manual_content = file.read()
258
 
259
- st.sidebar.markdown(
260
- manual_content
261
- )
262
-
263
-
264
- # CSS
265
-
266
- with open(
267
- "styles/main.css",
268
- "r",
269
- encoding="utf-8"
270
- ) as f:
271
 
 
 
272
  css = f.read()
273
 
274
- st.markdown(
275
- f"<style>{css}</style>",
276
- unsafe_allow_html=True
277
- )
278
-
279
-
280
- # Título
281
-
282
- st.markdown(
283
- "<h1 style='text-align:center;'>Enchanted Hooks</h1>",
284
- unsafe_allow_html=True
285
- )
286
 
 
 
287
  st.markdown(
288
-
289
- "<h4 style='text-align:center;'>"
290
- "Transforma cada idea en un titular que captura atención."
291
- "</h4>",
292
-
293
  unsafe_allow_html=True
294
-
295
- )
296
-
297
-
298
- # Layout
299
-
300
- col1, col2 = st.columns(
301
- [1, 2]
302
  )
303
 
 
 
304
 
305
- # Inputs
306
-
307
  with col1:
308
-
309
  target_audience = st.text_input(
310
-
311
  "¿Quién es tu público objetivo?",
312
-
313
- placeholder="Ejemplo: Coaches"
314
-
315
  )
316
-
317
  product = st.text_input(
318
-
319
- "¿Qué producto tienes?",
320
-
321
- placeholder="Ejemplo: Curso"
322
-
323
  )
324
-
325
  number_of_headlines = st.selectbox(
326
-
327
  "Número de Titulares",
328
-
329
- options=list(
330
- range(1, 11)
331
- ),
332
-
333
  index=4
334
-
335
  )
336
 
337
- with st.expander(
338
- "Personaliza tus titulares"
339
- ):
340
-
341
  temperature = st.slider(
342
-
343
  "Creatividad",
344
-
345
  min_value=0.0,
346
-
347
  max_value=2.0,
348
-
349
  value=1.0,
350
-
351
  step=0.1
352
-
353
  )
354
 
355
  selected_formula_key = st.selectbox(
356
-
357
- "Selecciona una fórmula",
358
-
359
- options=list(
360
- headline_formulas.keys()
361
- )
362
-
363
- )
364
-
365
- angle_keys = (
366
-
367
- ["NINGUNO"]
368
-
369
- +
370
-
371
- sorted(
372
-
373
- [
374
-
375
- key
376
-
377
- for key in angles.keys()
378
-
379
- if key != "NINGUNO"
380
-
381
- ]
382
-
383
- )
384
-
385
  )
386
 
 
 
387
  selected_angle = st.selectbox(
388
-
389
- "Selecciona el ángulo",
390
-
391
  options=angle_keys
392
-
393
  )
394
 
395
- selected_formula = headline_formulas[
396
- selected_formula_key
397
- ]
398
-
399
- submit = st.button(
400
- "Generar Titulares"
401
- )
402
-
403
 
404
- # =============================
405
- # RESULTADOS
406
- # =============================
407
 
 
408
  if submit:
 
 
 
409
 
410
- has_product = (
411
- product.strip() != ""
412
- )
413
-
414
- has_audience = (
415
- target_audience.strip() != ""
416
- )
417
-
418
- valid_inputs = (
419
- has_product
420
- and
421
- has_audience
422
- )
423
-
424
- if valid_inputs:
425
-
426
  try:
427
-
428
  generated_headlines = generate_headlines(
429
-
430
  number_of_headlines,
431
-
432
  target_audience,
433
-
434
  product,
435
-
436
  temperature,
437
-
438
  selected_formula_key,
439
-
440
  selected_formula,
441
-
442
  selected_angle
443
-
444
  )
445
 
446
- formatted_headlines = normalize_headlines(
447
- generated_headlines
448
- )
449
 
450
  col2.markdown(
451
-
452
  f"""
453
  <div class="results-container">
454
-
455
- <h4>
456
-
457
- Observa la magia en acción:
458
-
459
- </h4>
460
-
461
- <div>
462
-
463
- {formatted_headlines}
464
-
465
- </div>
466
-
467
  </div>
468
  """,
469
-
470
  unsafe_allow_html=True
471
-
472
  )
473
 
474
  except Exception as e:
475
-
476
- col2.error(
477
- f"Error: {str(e)}"
478
- )
479
-
480
  else:
481
-
482
- col2.error(
483
-
484
- "Por favor, proporciona el público objetivo y el producto."
485
-
486
- )
 
4
  from google import genai
5
  from google.genai import types
6
  import random
 
 
7
  from formulas import headline_formulas
8
  from angles import angles
9
 
10
+ # Cargar las variables de entorno
 
 
 
 
11
  load_dotenv()
12
 
13
+ # Configurar la API de Google
14
+ client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
 
17
+ def build_headline_context(selected_formula_key, selected_angle, target_audience, product):
 
 
 
 
 
 
 
 
 
18
  selected_formula = headline_formulas[selected_formula_key]
19
 
20
+ formula_description = selected_formula.get("description", "").strip()
21
+ formula_examples = selected_formula.get("examples", [])
 
 
 
 
 
22
 
23
  random_examples = random.sample(
24
  formula_examples,
 
28
  angle_instruction = ""
29
  angle_examples = []
30
 
31
+ if selected_angle != "NINGUNO" and selected_angle in angles:
32
+ angle_instruction = angles[selected_angle].get("instruction", "").strip()
33
+ angle_examples = angles[selected_angle].get("examples", [])[:3]
 
 
 
 
 
 
 
 
34
 
35
  extra_guidance = [
36
+ f"Escribe para {target_audience} como personas reales, no como una categoría genérica.",
37
+ f"Piensa en qué desea ese público, qué le frustra, qué objeciones tiene y qué transformación busca en relación con {product}.",
38
+ "No copies literalmente los ejemplos.",
39
+ "Haz que cada titular sea claro, atractivo y creíble.",
40
+ "No dependas de mencionar explícitamente el producto para generar interés."
 
 
 
 
 
 
41
  ]
42
 
43
  return {
44
+ "formula_description": formula_description,
45
+ "formula_examples": random_examples,
46
+ "angle_instruction": angle_instruction,
47
+ "angle_examples": angle_examples,
48
+ "extra_guidance": extra_guidance
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
 
52
+ def generate_headlines(number_of_headlines, target_audience, product, temperature, selected_formula_key, selected_formula, selected_angle):
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  context = build_headline_context(
54
+ selected_formula_key=selected_formula_key,
55
+ selected_angle=selected_angle,
56
+ target_audience=target_audience,
57
+ product=product
58
  )
59
 
60
+ system_prompt = """You are a world-class copywriter specialized in writing headlines, hooks, and subject lines that capture attention fast and spark curiosity.
 
 
61
 
62
  FORMAT RULES:
 
63
  - Each headline must start with number and period
64
  - One headline per line
65
+ - No explanations or categories
66
+ - Add a line break between each headline
67
+ - Avoid unnecessary : symbols
68
+ - Each headline must be a complete and intriguing sentence
69
+
70
+ IMPORTANT:
71
+ - Each headline must be unique and memorable
72
+ - Avoid clichés and generalities
73
+ - Maintain an intriguing but credible tone
74
+ - Adapt speaking language from the audience
75
+ - Focus on transformative benefits
76
  """
77
 
78
  headlines_instruction = f"{system_prompt}\n\n"
79
 
80
  if selected_angle != "NINGUNO":
 
81
  headlines_instruction += f"""
82
+ ÁNGULO PRINCIPAL: {selected_angle}
83
+ Aplica este ángulo como una capa de estilo sobre la fórmula, sin alterar su estructura base.
84
 
85
+ INSTRUCCIONES DE ÁNGULO ESPECÍFICAS:
 
 
 
 
 
86
  {context["angle_instruction"]}
87
 
88
+ EJEMPLOS EXITOSOS DEL ÁNGULO {selected_angle}:
89
  """
 
90
  for example in context["angle_examples"]:
91
+ headlines_instruction += f"- {example}\n"
 
 
92
 
93
  headlines_instruction += (
94
+ f"\nTu tarea es crear {number_of_headlines} titulares irresistibles para {target_audience} "
95
+ f"que capturen la atención instantáneamente y generen curiosidad sobre {product}. "
 
 
 
 
 
96
  )
97
 
98
  headlines_instruction += (
99
+ f"Antes de escribir, determina quién es realmente {target_audience}: "
100
+ f"qué desea, qué le frustra, qué objeciones tiene, qué transformación busca y cómo suele pensar o hablar sobre este problema. "
101
+ f"Usa esa comprensión para que cada titular conecte con su situación de forma natural y relevante.\n\n"
102
  )
103
 
104
+ if selected_angle != "NINGUNO":
 
 
105
  headlines_instruction += (
106
+ f"IMPORTANTE: Cada titular DEBE seguir el ángulo {selected_angle} de manera clara y consistente.\n\n"
107
  )
108
 
 
 
109
  headlines_instruction += (
110
+ f"No dependas de mencionar explícitamente {product} para generar interés genuino"
111
  )
112
 
113
+ if selected_angle != "NINGUNO":
114
+ headlines_instruction += " usando el ángulo seleccionado"
 
 
 
 
 
115
 
116
+ headlines_instruction += ".\n\n"
117
+
118
+ headlines_instruction += "GUÍA ADICIONAL:\n"
119
+ for rule in context["extra_guidance"]:
120
+ headlines_instruction += f"- {rule}\n"
121
  headlines_instruction += "\n"
122
 
123
  headlines_instruction += (
124
+ "IMPORTANTE: Estudia cuidadosamente estos ejemplos de la fórmula seleccionada. "
125
+ "Cada ejemplo representa el estilo y estructura a seguir"
126
  )
127
 
128
+ if selected_angle != "NINGUNO":
129
+ headlines_instruction += f", adaptados al ángulo {selected_angle}"
130
 
131
+ headlines_instruction += ":\n\n"
132
 
133
+ random_examples = context["formula_examples"]
134
 
135
+ headlines_instruction += "EJEMPLOS DE LA FÓRMULA A SEGUIR:\n"
136
+ for i, example in enumerate(random_examples, 1):
137
+ headlines_instruction += f"{i}. {example}\n"
138
 
139
+ headlines_instruction += "\nINSTRUCCIONES ESPECÍFICAS:\n"
140
+ headlines_instruction += "1. Mantén una estructura y longitud similares a las de los ejemplos anteriores\n"
141
+ headlines_instruction += "2. Conserva el nivel de especificidad y detalle\n"
142
+ headlines_instruction += "3. Inspírate en los patrones de construcción sin copiarlos literalmente\n"
143
+ headlines_instruction += "4. Haz que cada titular suene natural para el público objetivo\n"
144
+ headlines_instruction += "5. Asegúrate de que cada titular sea claro, atractivo y creíble\n\n"
145
 
146
+ headlines_instruction += f"FÓRMULA A SEGUIR:\n{context['formula_description']}\n\n"
147
 
148
+ if selected_angle != "NINGUNO":
149
+ headlines_instruction += f"""
150
+ RECORDATORIO FINAL:
151
+ 1. Sigue la estructura de la fórmula seleccionada
152
+ 2. Aplica el ángulo como una capa de estilo
153
+ 3. Mantén la coherencia entre fórmula y ángulo
154
+ 4. Asegura que cada titular refleje ambos elementos
155
+
156
+ GENERA AHORA:
157
+ Crea {number_of_headlines} titulares que sigan fielmente la estructura de la fórmula y mantengan la esencia de los ejemplos mostrados.
158
+ """
159
+ else:
160
+ headlines_instruction += f"""
161
+ GENERA AHORA:
162
+ Crea {number_of_headlines} titulares que sigan fielmente la estructura de la fórmula y mantengan la esencia de los ejemplos mostrados.
163
+ """
164
 
165
+ response = client.models.generate_content(
166
+ model="gemini-2.5-flash-lite",
167
+ contents=headlines_instruction + "\n\nGenera titulares originales que respeten la estructura de la fórmula seleccionada, apliquen el ángulo elegido y mantengan la esencia de los ejemplos sin copiarlos literalmente.",
168
+ config=types.GenerateContentConfig(
169
+ temperature=temperature,
170
+ top_p=0.65,
171
+ max_output_tokens=8196,
172
  ),
173
  )
174
 
175
  return response.text
176
 
177
 
178
+ # Configurar la interfaz de usuario con Streamlit
179
+ st.set_page_config(page_title="Enchanted Hooks", layout="wide")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ # Leer el contenido del archivo manual.md
182
+ with open("manual.md", "r", encoding="utf-8") as file:
183
  manual_content = file.read()
184
 
185
+ # Mostrar el contenido del manual en el sidebar
186
+ st.sidebar.markdown(manual_content)
 
 
 
 
 
 
 
 
 
 
187
 
188
+ # Load CSS from file
189
+ with open("styles/main.css", "r", encoding="utf-8") as f:
190
  css = f.read()
191
 
192
+ # Apply the CSS
193
+ st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
194
 
195
+ # Centrar el título y el subtítulo
196
+ st.markdown("<h1 style='text-align: center;'>Enchanted Hooks</h1>", unsafe_allow_html=True)
197
  st.markdown(
198
+ "<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>",
 
 
 
 
199
  unsafe_allow_html=True
 
 
 
 
 
 
 
 
200
  )
201
 
202
+ # Crear columnas
203
+ col1, col2 = st.columns([1, 2])
204
 
205
+ # Columnas de entrada
 
206
  with col1:
 
207
  target_audience = st.text_input(
 
208
  "¿Quién es tu público objetivo?",
209
+ placeholder="Ejemplo: Estudiantes Universitarios"
 
 
210
  )
 
211
  product = st.text_input(
212
+ "¿Qué producto tienes en mente?",
213
+ placeholder="Ejemplo: Curso de Inglés"
 
 
 
214
  )
 
215
  number_of_headlines = st.selectbox(
 
216
  "Número de Titulares",
217
+ options=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 
 
 
 
218
  index=4
 
219
  )
220
 
221
+ # Crear un único acordeón para fórmula, creatividad y ángulo
222
+ with st.expander("Personaliza tus titulares"):
 
 
223
  temperature = st.slider(
 
224
  "Creatividad",
 
225
  min_value=0.0,
 
226
  max_value=2.0,
 
227
  value=1.0,
 
228
  step=0.1
 
229
  )
230
 
231
  selected_formula_key = st.selectbox(
232
+ "Selecciona una fórmula para tus titulares",
233
+ options=list(headline_formulas.keys())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  )
235
 
236
+ # Make sure "NINGUNO" appears first, then the rest alphabetically
237
+ angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"])
238
  selected_angle = st.selectbox(
239
+ "Selecciona el ángulo para tus titulares",
 
 
240
  options=angle_keys
 
241
  )
242
 
243
+ selected_formula = headline_formulas[selected_formula_key]
 
 
 
 
 
 
 
244
 
245
+ # Botón de enviar
246
+ submit = st.button("Generar Titulares")
 
247
 
248
+ # Mostrar los titulares generados
249
  if submit:
250
+ has_product = product.strip() != ""
251
+ has_audience = target_audience.strip() != ""
252
+ valid_inputs = has_product and has_audience
253
 
254
+ if valid_inputs and selected_formula:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  try:
 
256
  generated_headlines = generate_headlines(
 
257
  number_of_headlines,
 
258
  target_audience,
 
259
  product,
 
260
  temperature,
 
261
  selected_formula_key,
 
262
  selected_formula,
 
263
  selected_angle
 
264
  )
265
 
266
+ formatted_headlines = generated_headlines.replace("\n", "<br>")
 
 
267
 
268
  col2.markdown(
 
269
  f"""
270
  <div class="results-container">
271
+ <h4>Observa la magia en acción:</h4>
272
+ <div>{formatted_headlines}</div>
 
 
 
 
 
 
 
 
 
 
 
273
  </div>
274
  """,
 
275
  unsafe_allow_html=True
 
276
  )
277
 
278
  except Exception as e:
279
+ col2.error(f"Error: {str(e)}")
 
 
 
 
280
  else:
281
+ if not selected_formula:
282
+ col2.error("Por favor, selecciona una fórmula.")
283
+ else:
284
+ col2.error("Por favor, proporciona el público objetivo y el producto.")