362 lines
13 KiB
Python
362 lines
13 KiB
Python
"""
|
|
Google Ads Generator - Interface Streamlit
|
|
|
|
Aplicação que extrai conteúdo de Landing Pages e gera automaticamente
|
|
ativos de campanha para Google Ads usando IA (OpenAI ou Gemini).
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import streamlit as st
|
|
from dotenv import load_dotenv
|
|
|
|
from src.scraper import scrape_landing_page
|
|
from src.ai_generator import generate_google_ads_assets, MODELS
|
|
from src.exporter import (
|
|
create_keywords_df,
|
|
create_negative_keywords_df,
|
|
create_ads_df,
|
|
create_sitelinks_df,
|
|
create_callouts_df,
|
|
export_all_to_excel,
|
|
)
|
|
|
|
# ─── Carregar variáveis de ambiente ───────────────────────────────
|
|
load_dotenv()
|
|
|
|
|
|
def _highlight_keywords(text: str, keywords: list[str]) -> str:
|
|
"""Destaca em negrito as palavras-chave encontradas no texto."""
|
|
result = text
|
|
for kw in keywords:
|
|
pattern = re.compile(re.escape(kw), re.IGNORECASE)
|
|
match = pattern.search(result)
|
|
if match:
|
|
result = result[:match.start()] + f"**{match.group()}**" + result[match.end():]
|
|
break # Destacar apenas a primeira keyword encontrada
|
|
return result
|
|
|
|
# ─── Configuração da Página ───────────────────────────────────────
|
|
st.set_page_config(
|
|
page_title="Google Ads Generator",
|
|
page_icon="📊",
|
|
layout="wide",
|
|
)
|
|
|
|
# ─── CSS Customizado ──────────────────────────────────────────────
|
|
st.markdown("""
|
|
<style>
|
|
.main-header {
|
|
font-size: 2.2rem;
|
|
font-weight: 700;
|
|
color: #1a73e8;
|
|
margin-bottom: 0.2rem;
|
|
}
|
|
.sub-header {
|
|
font-size: 1.1rem;
|
|
color: #5f6368;
|
|
margin-bottom: 2rem;
|
|
}
|
|
.metric-card {
|
|
background: #f8f9fa;
|
|
border-radius: 12px;
|
|
padding: 1.2rem;
|
|
text-align: center;
|
|
border: 1px solid #e8eaed;
|
|
}
|
|
.metric-number {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
color: #1a73e8;
|
|
}
|
|
.metric-label {
|
|
font-size: 0.85rem;
|
|
color: #5f6368;
|
|
}
|
|
.stTabs [data-baseweb="tab-list"] {
|
|
gap: 8px;
|
|
}
|
|
.stTabs [data-baseweb="tab"] {
|
|
padding: 10px 20px;
|
|
border-radius: 8px 8px 0 0;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
# ─── Header ───────────────────────────────────────────────────────
|
|
st.markdown('<p class="main-header">Google Ads Generator</p>', unsafe_allow_html=True)
|
|
st.markdown(
|
|
'<p class="sub-header">Gere ativos de campanha automaticamente a partir da sua Landing Page</p>',
|
|
unsafe_allow_html=True,
|
|
)
|
|
|
|
# ─── Sidebar ──────────────────────────────────────────────────────
|
|
with st.sidebar:
|
|
st.header("Configurações")
|
|
|
|
provider = st.selectbox(
|
|
"Provider de IA",
|
|
["OpenAI", "Gemini"],
|
|
index=0,
|
|
help="Escolha o provider de IA. As chaves devem estar configuradas no arquivo .env",
|
|
)
|
|
|
|
model = st.selectbox(
|
|
"Modelo",
|
|
MODELS.get(provider, []),
|
|
index=0,
|
|
help="Modelo a ser utilizado para geração dos ativos.",
|
|
)
|
|
|
|
# Status das chaves configuradas
|
|
st.divider()
|
|
openai_ok = bool(os.environ.get("OPENAI_API_KEY"))
|
|
gemini_ok = bool(os.environ.get("GEMINI_API_KEY"))
|
|
st.caption("Status das API Keys (.env):")
|
|
st.markdown(f"- OpenAI: {'✅ Configurada' if openai_ok else '❌ Não encontrada'}")
|
|
st.markdown(f"- Gemini: {'✅ Configurada' if gemini_ok else '❌ Não encontrada'}")
|
|
|
|
st.divider()
|
|
|
|
campaign_name = st.text_input(
|
|
"Nome da Campanha",
|
|
value="Campanha LP",
|
|
help="Nome que aparecerá na coluna 'Campaign' do CSV.",
|
|
)
|
|
|
|
ad_group = st.text_input(
|
|
"Nome do Grupo de Anúncios",
|
|
value="Grupo 1",
|
|
help="Nome que aparecerá na coluna 'Ad Group' do CSV.",
|
|
)
|
|
|
|
st.divider()
|
|
st.caption("Desenvolvido com Streamlit + OpenAI + Gemini")
|
|
|
|
# ─── Área Principal ───────────────────────────────────────────────
|
|
url = st.text_input(
|
|
"URL da Landing Page",
|
|
placeholder="https://www.seusite.com.br/landing-page",
|
|
help="Cole a URL completa da sua Landing Page aqui.",
|
|
)
|
|
|
|
col_btn, col_status = st.columns([1, 3])
|
|
|
|
with col_btn:
|
|
generate_btn = st.button("Gerar Campanha", type="primary", use_container_width=True)
|
|
|
|
# ─── Lógica Principal ────────────────────────────────────────────
|
|
if generate_btn:
|
|
# Validações
|
|
if not url:
|
|
st.error("Por favor, insira a URL da Landing Page.")
|
|
st.stop()
|
|
|
|
if not url.startswith(("http://", "https://")):
|
|
st.error("A URL deve começar com http:// ou https://")
|
|
st.stop()
|
|
|
|
# Verificar se a chave do provider selecionado está configurada
|
|
if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"):
|
|
st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.")
|
|
st.stop()
|
|
elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"):
|
|
st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.")
|
|
st.stop()
|
|
|
|
# Step 1: Scraping
|
|
with st.status("Processando...", expanded=True) as status:
|
|
st.write("Extraindo conteúdo da Landing Page...")
|
|
try:
|
|
lp_data = scrape_landing_page(url)
|
|
except Exception as e:
|
|
st.error(f"Erro ao acessar a URL: {e}")
|
|
st.stop()
|
|
|
|
st.write(f"Conteúdo extraído: {len(lp_data['paragraphs'])} parágrafos, "
|
|
f"{len(lp_data['h1'])} H1, {len(lp_data['h2'])} H2, "
|
|
f"{len(lp_data.get('list_items', []))} itens de lista, "
|
|
f"{len(lp_data['ctas'])} CTAs")
|
|
|
|
# Step 2: IA
|
|
st.write(f"Gerando ativos com {provider} ({model})...")
|
|
try:
|
|
assets, prompts = generate_google_ads_assets(
|
|
lp_content=lp_data["full_text"],
|
|
provider=provider,
|
|
model=model,
|
|
)
|
|
except ValueError as e:
|
|
st.error(str(e))
|
|
st.stop()
|
|
except Exception as e:
|
|
st.error(f"Erro ao gerar ativos: {e}")
|
|
st.stop()
|
|
|
|
status.update(label="Concluído!", state="complete", expanded=False)
|
|
|
|
# Salvar no session_state
|
|
st.session_state["assets"] = assets
|
|
st.session_state["prompts"] = prompts
|
|
st.session_state["lp_data"] = lp_data
|
|
st.session_state["campaign_name"] = campaign_name
|
|
st.session_state["ad_group"] = ad_group
|
|
st.session_state["provider_used"] = provider
|
|
st.session_state["model_used"] = model
|
|
|
|
# ─── Exibição dos Resultados ─────────────────────────────────────
|
|
if "assets" in st.session_state:
|
|
assets = st.session_state["assets"]
|
|
prompts = st.session_state.get("prompts", {})
|
|
camp = st.session_state.get("campaign_name", "Campanha LP")
|
|
adg = st.session_state.get("ad_group", "Grupo 1")
|
|
prov_used = st.session_state.get("provider_used", "")
|
|
model_used = st.session_state.get("model_used", "")
|
|
|
|
st.divider()
|
|
|
|
# Métricas resumo
|
|
col1, col2, col3, col4, col5, col6 = st.columns(6)
|
|
|
|
with col1:
|
|
st.metric("Keywords", len(assets.get("keywords", [])))
|
|
with col2:
|
|
st.metric("Negativas", len(assets.get("negative_keywords", [])))
|
|
with col3:
|
|
st.metric("Títulos", len(assets.get("headlines", [])))
|
|
with col4:
|
|
st.metric("Descrições", len(assets.get("descriptions", [])))
|
|
with col5:
|
|
st.metric("Sitelinks", len(assets.get("sitelinks", [])))
|
|
with col6:
|
|
st.metric("Callouts", len(assets.get("callouts", [])))
|
|
|
|
st.divider()
|
|
|
|
# Abas com resultados
|
|
tab_kw, tab_neg, tab_ads, tab_sl, tab_co, tab_prompts, tab_raw = st.tabs([
|
|
"Keywords", "Negativas", "Anúncios RSA", "Sitelinks", "Callouts",
|
|
"Prompts Utilizados", "Dados da LP",
|
|
])
|
|
|
|
with tab_kw:
|
|
st.subheader("Palavras-chave")
|
|
kw_df = create_keywords_df(assets, camp, adg)
|
|
if not kw_df.empty:
|
|
st.dataframe(kw_df, use_container_width=True, hide_index=True)
|
|
else:
|
|
st.info("Nenhuma palavra-chave gerada.")
|
|
|
|
with tab_neg:
|
|
st.subheader("Palavras-chave Negativas")
|
|
nkw_df = create_negative_keywords_df(assets, camp)
|
|
if not nkw_df.empty:
|
|
st.dataframe(nkw_df, use_container_width=True, hide_index=True)
|
|
else:
|
|
st.info("Nenhuma palavra-chave negativa gerada.")
|
|
|
|
with tab_ads:
|
|
st.subheader("Anúncio Responsivo de Pesquisa (RSA)")
|
|
|
|
st.write("**Títulos (Headlines):**")
|
|
# Coletar palavras-chave para destacar nos títulos
|
|
kw_list = sorted(
|
|
[kw["keyword"].lower() for kw in assets.get("keywords", []) if kw.get("keyword")],
|
|
key=len, reverse=True, # Maior primeiro para evitar match parcial
|
|
)
|
|
for i, h in enumerate(assets.get("headlines", []), 1):
|
|
char_count = len(h)
|
|
color = "green" if char_count <= 30 else "red"
|
|
h_highlighted = _highlight_keywords(h, kw_list)
|
|
st.markdown(f"{i}. {h_highlighted} — :{color}[{char_count} chars]")
|
|
|
|
st.write("**Descrições:**")
|
|
for i, d in enumerate(assets.get("descriptions", []), 1):
|
|
char_count = len(d)
|
|
color = "green" if char_count <= 90 else "red"
|
|
st.markdown(f"{i}. {d} — :{color}[{char_count} chars]")
|
|
|
|
st.divider()
|
|
ads_df = create_ads_df(assets, camp, adg)
|
|
if not ads_df.empty:
|
|
st.dataframe(ads_df, use_container_width=True, hide_index=True)
|
|
|
|
with tab_sl:
|
|
st.subheader("Sitelinks")
|
|
sl_df = create_sitelinks_df(assets, camp)
|
|
if not sl_df.empty:
|
|
st.dataframe(sl_df, use_container_width=True, hide_index=True)
|
|
else:
|
|
st.info("Nenhum sitelink gerado.")
|
|
|
|
with tab_co:
|
|
st.subheader("Callouts (Frases de Destaque)")
|
|
co_df = create_callouts_df(assets, camp)
|
|
if not co_df.empty:
|
|
st.dataframe(co_df, use_container_width=True, hide_index=True)
|
|
else:
|
|
st.info("Nenhum callout gerado.")
|
|
|
|
with tab_prompts:
|
|
st.subheader("Prompts Utilizados")
|
|
st.caption(f"Provider: **{prov_used}** | Modelo: **{model_used}**")
|
|
|
|
st.markdown("#### Prompt de Sistema (System Prompt)")
|
|
st.code(prompts.get("system_prompt", "N/A"), language=None)
|
|
|
|
st.markdown("#### Prompt do Usuário (User Prompt)")
|
|
st.code(prompts.get("user_prompt", "N/A"), language=None)
|
|
|
|
with tab_raw:
|
|
st.subheader("Dados Extraídos da Landing Page")
|
|
lp_data = st.session_state.get("lp_data", {})
|
|
if lp_data:
|
|
st.write(f"**URL:** {lp_data.get('url', '')}")
|
|
st.write(f"**Título:** {lp_data.get('title', '')}")
|
|
st.write(f"**Meta Description:** {lp_data.get('meta_description', '')}")
|
|
|
|
with st.expander("Headings (H1, H2, H3)"):
|
|
for h in lp_data.get("h1", []):
|
|
st.write(f"**H1:** {h}")
|
|
for h in lp_data.get("h2", []):
|
|
st.write(f"**H2:** {h}")
|
|
for h in lp_data.get("h3", []):
|
|
st.write(f"**H3:** {h}")
|
|
|
|
with st.expander("Itens de Lista / Benefícios"):
|
|
for li in lp_data.get("list_items", []):
|
|
st.write(f"- {li}")
|
|
|
|
with st.expander("CTAs Encontrados"):
|
|
for c in lp_data.get("ctas", []):
|
|
st.write(f"- {c}")
|
|
|
|
with st.expander("Texto Completo Enviado à IA"):
|
|
st.code(lp_data.get("full_text", ""), language=None)
|
|
|
|
# ─── Botões de Download ───────────────────────────────────────
|
|
st.divider()
|
|
st.subheader("Download dos Ativos")
|
|
|
|
col_dl1, col_dl2 = st.columns(2)
|
|
|
|
with col_dl1:
|
|
excel_data = export_all_to_excel(assets, camp, adg)
|
|
st.download_button(
|
|
label="Baixar Excel (.xlsx)",
|
|
data=excel_data,
|
|
file_name="google_ads_assets.xlsx",
|
|
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
use_container_width=True,
|
|
)
|
|
|
|
with col_dl2:
|
|
kw_csv = create_keywords_df(assets, camp, adg).to_csv(index=False)
|
|
st.download_button(
|
|
label="Baixar Keywords (.csv)",
|
|
data=kw_csv,
|
|
file_name="google_ads_keywords.csv",
|
|
mime="text/csv",
|
|
use_container_width=True,
|
|
)
|