Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 20 |
-
|
| 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 |
-
|
| 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 |
-
|
| 77 |
-
|
| 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"
|
| 90 |
-
|
| 91 |
-
|
| 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 |
-
"
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
"
|
| 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 |
-
-
|
| 151 |
-
-
|
| 152 |
-
-
|
| 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
|
| 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"
|
| 180 |
-
|
| 181 |
-
f"para {target_audience} "
|
| 182 |
-
|
| 183 |
-
f"sobre {product}.\n\n"
|
| 184 |
-
|
| 185 |
)
|
| 186 |
|
| 187 |
headlines_instruction += (
|
| 188 |
-
"Antes de escribir,
|
|
|
|
|
|
|
| 189 |
)
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
for rule in context["extra_guidance"]:
|
| 194 |
headlines_instruction += (
|
| 195 |
-
f"
|
| 196 |
)
|
| 197 |
|
| 198 |
-
headlines_instruction += "\n"
|
| 199 |
-
|
| 200 |
headlines_instruction += (
|
| 201 |
-
"
|
| 202 |
)
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
1
|
| 207 |
-
):
|
| 208 |
-
headlines_instruction += (
|
| 209 |
-
f"{i}. {example}\n"
|
| 210 |
-
)
|
| 211 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
headlines_instruction += "\n"
|
| 213 |
|
| 214 |
headlines_instruction += (
|
| 215 |
-
|
| 216 |
-
|
| 217 |
)
|
| 218 |
|
| 219 |
-
|
|
|
|
| 220 |
|
| 221 |
-
|
| 222 |
|
| 223 |
-
|
| 224 |
|
| 225 |
-
|
|
|
|
|
|
|
| 226 |
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
|
| 230 |
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
),
|
| 234 |
)
|
| 235 |
|
| 236 |
return response.text
|
| 237 |
|
| 238 |
|
| 239 |
-
#
|
| 240 |
-
|
| 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 |
-
|
| 260 |
-
|
| 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 |
-
|
| 275 |
-
|
| 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 |
-
#
|
| 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 |
-
"
|
| 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 |
-
|
| 338 |
-
|
| 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 |
-
|
| 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 |
-
|
| 406 |
-
# =============================
|
| 407 |
|
|
|
|
| 408 |
if submit:
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
-
|
| 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 =
|
| 447 |
-
generated_headlines
|
| 448 |
-
)
|
| 449 |
|
| 450 |
col2.markdown(
|
| 451 |
-
|
| 452 |
f"""
|
| 453 |
<div class="results-container">
|
| 454 |
-
|
| 455 |
-
<
|
| 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 |
-
|
| 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.")
|
|
|
|
|
|