Spaces:
Sleeping
Sleeping
Update app.py
Browse filesMelhorias na aba Chatbot Assistente
app.py
CHANGED
|
@@ -139,12 +139,18 @@ CACHE_DIR.mkdir(exist_ok=True)
|
|
| 139 |
def _inicializar_firestore():
|
| 140 |
"""
|
| 141 |
Inicializa o Firebase Admin SDK usando as credenciais
|
| 142 |
-
armazenadas nos Secrets do Hugging Face Spaces.
|
| 143 |
"""
|
| 144 |
global db, analytics
|
| 145 |
-
|
|
|
|
| 146 |
secret_name = "FIREBASE_SERVICE_ACCOUNT_JSON"
|
| 147 |
-
secret_json_string =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
if not secret_json_string:
|
| 150 |
print(f"❌ Erro de Configuração do Firebase: Secret '{secret_name}' não encontrado.")
|
|
@@ -252,18 +258,18 @@ def _salvar_analytics_firestore():
|
|
| 252 |
def atualizar_analytics(nicho, estilo, palavras, imagem_gerada, cache_hit, favorito):
|
| 253 |
"""Atualiza as métricas de analytics (agora salva no Firestore)."""
|
| 254 |
global analytics
|
| 255 |
-
|
| 256 |
analytics['total_posts'] = analytics.get('total_posts', 0) + 1
|
| 257 |
analytics['total_palavras'] = analytics.get('total_palavras', 0) + palavras
|
| 258 |
-
|
| 259 |
if imagem_gerada:
|
| 260 |
analytics['total_imagens'] = analytics.get('total_imagens', 0) + 1
|
| 261 |
-
|
| 262 |
if cache_hit:
|
| 263 |
analytics['cache_hits'] = analytics.get('cache_hits', 0) + 1
|
| 264 |
else:
|
| 265 |
analytics['cache_misses'] = analytics.get('cache_misses', 0) + 1
|
| 266 |
-
|
| 267 |
if favorito:
|
| 268 |
analytics['total_favoritos'] = analytics.get('total_favoritos', 0) + 1
|
| 269 |
|
|
@@ -271,11 +277,11 @@ def atualizar_analytics(nicho, estilo, palavras, imagem_gerada, cache_hit, favor
|
|
| 271 |
nicho_counts = analytics.get('posts_por_nicho', {})
|
| 272 |
nicho_counts[nicho] = nicho_counts.get(nicho, 0) + 1
|
| 273 |
analytics['posts_por_nicho'] = nicho_counts
|
| 274 |
-
|
| 275 |
estilo_counts = analytics.get('posts_por_estilo', {})
|
| 276 |
estilo_counts[estilo] = estilo_counts.get(estilo, 0) + 1
|
| 277 |
analytics['posts_por_estilo'] = estilo_counts
|
| 278 |
-
|
| 279 |
# Salvar no Firestore
|
| 280 |
_salvar_analytics_firestore()
|
| 281 |
|
|
@@ -284,14 +290,14 @@ def gerar_relatorio_analytics():
|
|
| 284 |
global analytics
|
| 285 |
if not analytics or 'status' in analytics or analytics.get("total_posts", 0) == 0:
|
| 286 |
return "📊 Nenhum post gerado ainda."
|
| 287 |
-
|
| 288 |
# Ordenar os dicionários por valor (mais usados primeiro)
|
| 289 |
posts_por_nicho_sorted = dict(sorted(analytics.get('posts_por_nicho', {}).items(), key=lambda item: item[1], reverse=True))
|
| 290 |
posts_por_estilo_sorted = dict(sorted(analytics.get('posts_por_estilo', {}).items(), key=lambda item: item[1], reverse=True))
|
| 291 |
|
| 292 |
total_reqs = analytics.get('cache_hits', 0) + analytics.get('cache_misses', 0)
|
| 293 |
taxa_cache_hit = (analytics.get('cache_hits', 0) / total_reqs * 100) if total_reqs > 0 else 0
|
| 294 |
-
|
| 295 |
nicho_top = max(analytics["posts_por_nicho"].items(), key=lambda x: x[1]) if analytics.get("posts_por_nicho") else ("N/A", 0)
|
| 296 |
estilo_top = max(analytics["posts_por_estilo"].items(), key=lambda x: x[1]) if analytics.get("posts_por_estilo") else ("N/A", 0)
|
| 297 |
|
|
@@ -357,18 +363,18 @@ def buscar_no_cache(key):
|
|
| 357 |
try:
|
| 358 |
with open(cache_file, 'r', encoding='utf-8') as f:
|
| 359 |
data = json.load(f)
|
| 360 |
-
|
| 361 |
texto = data.get("texto")
|
| 362 |
imagem_path = data.get("imagem_path")
|
| 363 |
imagem = None
|
| 364 |
-
|
| 365 |
if imagem_path:
|
| 366 |
img_cache_file = CACHE_DIR / imagem_path
|
| 367 |
if img_cache_file.exists():
|
| 368 |
imagem = Image.open(img_cache_file)
|
| 369 |
else:
|
| 370 |
return None, None
|
| 371 |
-
|
| 372 |
return texto, imagem
|
| 373 |
except Exception as e:
|
| 374 |
print(f"Erro ao ler cache {key}: {e}")
|
|
@@ -379,7 +385,7 @@ def salvar_imagem_cache(key, imagem_pil):
|
|
| 379 |
"""Salva a imagem PIL no diretório de cache e retorna o nome do arquivo."""
|
| 380 |
if not imagem_pil:
|
| 381 |
return None
|
| 382 |
-
|
| 383 |
try:
|
| 384 |
imagem_path = f"{key}_img.png"
|
| 385 |
imagem_pil.save(CACHE_DIR / imagem_path)
|
|
@@ -437,6 +443,10 @@ def copiar_feedback(texto):
|
|
| 437 |
return criar_alerta('success', '✅ Texto copiado!')
|
| 438 |
return criar_alerta('warning', '⚠️ Nada para copiar')
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
def limpar_tudo():
|
| 441 |
"""Limpa todos os inputs da UI, incluindo filtros de histórico."""
|
| 442 |
analytics_data = gerar_relatorio_analytics() # Gerar relatório limpo
|
|
@@ -506,34 +516,34 @@ def interpretar_erro_api(erro_str):
|
|
| 506 |
def filtrar_historico_local(query, nicho, estilo, formato, favoritos_apenas):
|
| 507 |
"""Filtra a lista global `post_history` com base nos inputs da UI."""
|
| 508 |
global post_history
|
| 509 |
-
|
| 510 |
# Começa com a lista completa
|
| 511 |
resultados = post_history
|
| 512 |
-
|
| 513 |
# Filtro de busca por texto
|
| 514 |
if query:
|
| 515 |
query_lower = query.lower()
|
| 516 |
resultados = [
|
| 517 |
-
post for post in resultados
|
| 518 |
if query_lower in post.get("Tema", "").lower() or query_lower in post.get("Texto", "").lower()
|
| 519 |
]
|
| 520 |
-
|
| 521 |
# Filtro de Nicho
|
| 522 |
if nicho != "Todos":
|
| 523 |
resultados = [post for post in resultados if post.get("Nicho") == nicho]
|
| 524 |
-
|
| 525 |
# Filtro de Estilo
|
| 526 |
if estilo != "Todos":
|
| 527 |
resultados = [post for post in resultados if post.get("Estilo") == estilo]
|
| 528 |
-
|
| 529 |
# Filtro de Formato
|
| 530 |
if formato != "Todos":
|
| 531 |
resultados = [post for post in resultados if post.get("Formato") == formato]
|
| 532 |
-
|
| 533 |
# Filtro de Favoritos
|
| 534 |
if favoritos_apenas:
|
| 535 |
resultados = [post for post in resultados if post.get("Favorito") == True]
|
| 536 |
-
|
| 537 |
# Formata para o Dataframe
|
| 538 |
return _formatar_historico_para_df(resultados)
|
| 539 |
|
|
@@ -546,7 +556,7 @@ def carregar_post_do_historico(evt: gr.SelectData):
|
|
| 546 |
try:
|
| 547 |
# Pega o post correspondente da lista global
|
| 548 |
post_selecionado = post_history[evt.index]
|
| 549 |
-
|
| 550 |
# Extrai os dados
|
| 551 |
tema = post_selecionado.get("Tema", "")
|
| 552 |
nicho = post_selecionado.get("Nicho", NICHOS_DISPONIVEIS[0])
|
|
@@ -554,10 +564,10 @@ def carregar_post_do_historico(evt: gr.SelectData):
|
|
| 554 |
formato = post_selecionado.get("Formato", list(FORMATO_CONFIGS.keys())[0])
|
| 555 |
favorito = post_selecionado.get("Favorito", False)
|
| 556 |
texto = post_selecionado.get("Texto", "")
|
| 557 |
-
|
| 558 |
# Feedback para o usuário
|
| 559 |
status_alerta = criar_alerta('info', '✅ Post carregado do histórico! A imagem deve ser gerada novamente se desejado.')
|
| 560 |
-
|
| 561 |
# Retorna os valores para a UI, incluindo a mudança de aba
|
| 562 |
return (
|
| 563 |
gr.Tabs(selected=0), # Muda para a primeira aba (Gerar Post)
|
|
@@ -585,12 +595,12 @@ def gerar_texto(tema, nicho, estilo, formato):
|
|
| 585 |
"""
|
| 586 |
Gera texto usando API do Hugging Face com base no formato escolhido.
|
| 587 |
"""
|
| 588 |
-
|
| 589 |
if not HUGGINGFACE_API_KEY:
|
| 590 |
return "❌ Erro de Configuração: API Key não está definida."
|
| 591 |
|
| 592 |
config = FORMATO_CONFIGS.get(formato, FORMATO_CONFIGS["Instagram (Post)"])
|
| 593 |
-
|
| 594 |
url = f"{BASE_URL}/chat/completions"
|
| 595 |
|
| 596 |
payload = {
|
|
@@ -646,7 +656,7 @@ def traduzir_texto(texto_pt):
|
|
| 646 |
|
| 647 |
try:
|
| 648 |
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
| 649 |
-
|
| 650 |
if response.status_code == 200:
|
| 651 |
resultado = response.json()
|
| 652 |
if resultado and isinstance(resultado, list) and 'translation_text' in resultado[0]:
|
|
@@ -662,12 +672,12 @@ def traduzir_texto(texto_pt):
|
|
| 662 |
|
| 663 |
def otimizar_prompt_imagem(descricao_pt, estilo_escolhido, filtro_escolhido):
|
| 664 |
"""Combina as escolhas do usuário em um prompt otimizado (em Português)."""
|
| 665 |
-
|
| 666 |
estilo = ESTILOS_DE_IMAGEM.get(estilo_escolhido, ESTILOS_DE_IMAGEM["Nenhum (Automático)"])
|
| 667 |
filtro = FILTROS_IMAGEM.get(filtro_escolhido, FILTROS_IMAGEM["Nenhum"])
|
| 668 |
-
|
| 669 |
prompt_final = f"{descricao_pt}, {estilo}, {filtro}, best quality, 4k"
|
| 670 |
-
|
| 671 |
prompt_final = prompt_final.replace(", ,", ",").replace(", ,", ",")
|
| 672 |
return prompt_final
|
| 673 |
|
|
@@ -680,7 +690,7 @@ def gerar_imagem_robusta(descricao_pt, estilo_escolhido, qualidade, filtro_escol
|
|
| 680 |
Gera imagem com sistema robusto de fallback e controle de qualidade.
|
| 681 |
Retorna: (PIL.Image, str_mensagem_status)
|
| 682 |
"""
|
| 683 |
-
|
| 684 |
# 1. Configs de Qualidade
|
| 685 |
configs_qualidade = {
|
| 686 |
"Rápida": {
|
|
@@ -697,7 +707,7 @@ def gerar_imagem_robusta(descricao_pt, estilo_escolhido, qualidade, filtro_escol
|
|
| 697 |
}
|
| 698 |
}
|
| 699 |
config = configs_qualidade.get(qualidade, configs_qualidade["Balanceada"])
|
| 700 |
-
|
| 701 |
# 2. Otimizar e Traduzir Prompt
|
| 702 |
if progress: progress(0.55, desc="🌍 Otimizando e traduzindo prompt...")
|
| 703 |
prompt_otimizado_pt = otimizar_prompt_imagem(descricao_pt, estilo_escolhido, filtro_escolhido)
|
|
@@ -710,26 +720,26 @@ def gerar_imagem_robusta(descricao_pt, estilo_escolhido, qualidade, filtro_escol
|
|
| 710 |
if progress:
|
| 711 |
prog_val = 0.6 + (i * 0.1) # Ajustar progresso
|
| 712 |
progress(prog_val, desc=f"🎨 Tentando {modelo_config['nome']}...")
|
| 713 |
-
|
| 714 |
print(f"Tentando gerar imagem com {modelo_config['nome']}...")
|
| 715 |
|
| 716 |
client = InferenceClient(api_key=HUGGINGFACE_API_KEY)
|
| 717 |
-
|
| 718 |
imagem = client.text_to_image(
|
| 719 |
prompt=prompt_final_en,
|
| 720 |
model=modelo_config['id'],
|
| 721 |
negative_prompt=negative_prompt,
|
| 722 |
num_inference_steps=config['steps']
|
| 723 |
)
|
| 724 |
-
|
| 725 |
print(f"✅ Imagem gerada com {modelo_config['nome']}")
|
| 726 |
mensagem = f"✅ Imagem gerada com {modelo_config['nome']}"
|
| 727 |
-
|
| 728 |
return (imagem, mensagem) # Retorna (PIL.Image, str)
|
| 729 |
|
| 730 |
except Exception as e:
|
| 731 |
print(f"❌ Falha com {modelo_config['nome']}: {str(e)}")
|
| 732 |
-
|
| 733 |
if i < len(config['modelos']) - 1:
|
| 734 |
print(f"⏭️ Tentando próximo modelo...")
|
| 735 |
continue
|
|
@@ -744,6 +754,10 @@ def gerar_imagem_robusta(descricao_pt, estilo_escolhido, qualidade, filtro_escol
|
|
| 744 |
# FUNÇÃO DO CHATBOT
|
| 745 |
# ============================================
|
| 746 |
def responder_chat(message, chat_history):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 747 |
if not HUGGINGFACE_API_KEY:
|
| 748 |
return "❌ Erro de Configuração: API Key não está definida."
|
| 749 |
|
|
@@ -751,8 +765,11 @@ def responder_chat(message, chat_history):
|
|
| 751 |
|
| 752 |
system_prompt = "Você é um assistente virtual prestativo e amigável, especializado em marketing de mídias sociais e criação de conteúdo, mas pode responder sobre qualquer tópico. Seja direto e útil."
|
| 753 |
|
|
|
|
| 754 |
messages = [{"role": "system", "content": system_prompt}]
|
|
|
|
| 755 |
messages.extend(chat_history)
|
|
|
|
| 756 |
messages.append({"role": "user", "content": message})
|
| 757 |
|
| 758 |
payload = {
|
|
@@ -779,6 +796,21 @@ def responder_chat(message, chat_history):
|
|
| 779 |
except Exception as e:
|
| 780 |
return f"❌ {interpretar_erro_api(str(e))}"
|
| 781 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 782 |
# ============================================
|
| 783 |
# FUNÇÕES DE DOWNLOAD
|
| 784 |
# ============================================
|
|
@@ -791,14 +823,14 @@ def criar_post_completo(texto, imagem_pil, tema):
|
|
| 791 |
if not imagem_pil or not texto:
|
| 792 |
print("Erro: Texto ou Imagem ausente para criar post.")
|
| 793 |
return None
|
| 794 |
-
|
| 795 |
try:
|
| 796 |
LARGURA_POST = 1080
|
| 797 |
ALTURA_IMAGEM = 1080
|
| 798 |
PADDING = 60
|
| 799 |
COR_FUNDO = (255, 255, 255)
|
| 800 |
COR_TEXTO = (0, 0, 0)
|
| 801 |
-
|
| 802 |
try:
|
| 803 |
fonte_texto = ImageFont.truetype("DejaVuSans.ttf", size=42)
|
| 804 |
fonte_titulo = ImageFont.truetype("DejaVuSans-Bold.ttf", size=55)
|
|
@@ -808,30 +840,30 @@ def criar_post_completo(texto, imagem_pil, tema):
|
|
| 808 |
fonte_titulo = ImageFont.load_default()
|
| 809 |
|
| 810 |
imagem_quadrada = imagem_pil.resize((LARGURA_POST, ALTURA_IMAGEM), Image.Resampling.LANCZOS)
|
| 811 |
-
|
| 812 |
linhas_titulo = textwrap.wrap(tema.upper(), width=40)
|
| 813 |
linhas_texto = textwrap.wrap(texto, width=50)
|
| 814 |
-
|
| 815 |
-
altura_titulo = len(linhas_titulo) * 60
|
| 816 |
altura_texto = len(linhas_texto) * 45
|
| 817 |
-
altura_total_texto = altura_titulo + 20 + altura_texto + (PADDING * 2)
|
| 818 |
-
|
| 819 |
altura_total = ALTURA_IMAGEM + altura_total_texto
|
| 820 |
post_completo = Image.new('RGB', (LARGURA_POST, int(altura_total)), COR_FUNDO)
|
| 821 |
-
|
| 822 |
post_completo.paste(imagem_quadrada, (0, 0))
|
| 823 |
-
|
| 824 |
draw = ImageDraw.Draw(post_completo)
|
| 825 |
pos_y = ALTURA_IMAGEM + PADDING
|
| 826 |
-
|
| 827 |
for linha in linhas_titulo:
|
| 828 |
largura_linha = draw.textlength(linha, font=fonte_titulo)
|
| 829 |
pos_x_titulo = (LARGURA_POST - largura_linha) / 2
|
| 830 |
draw.text((pos_x_titulo, pos_y), linha, font=fonte_titulo, fill=COR_TEXTO)
|
| 831 |
pos_y += 60
|
| 832 |
-
|
| 833 |
pos_y += 20
|
| 834 |
-
|
| 835 |
for linha in linhas_texto:
|
| 836 |
draw.text((PADDING, pos_y), linha, font=fonte_texto, fill=COR_TEXTO)
|
| 837 |
pos_y += 45
|
|
@@ -840,7 +872,7 @@ def criar_post_completo(texto, imagem_pil, tema):
|
|
| 840 |
post_completo.save(f, 'PNG')
|
| 841 |
print(f"Arquivo temporário salvo em: {f.name}")
|
| 842 |
return f.name
|
| 843 |
-
|
| 844 |
except Exception as e:
|
| 845 |
print(f"❌ Erro ao criar post completo: {e}")
|
| 846 |
return None
|
|
@@ -857,14 +889,14 @@ def preparar_download(texto, imagem_pil, tema):
|
|
| 857 |
|
| 858 |
if caminho_arquivo:
|
| 859 |
return caminho_arquivo
|
| 860 |
-
|
| 861 |
return None
|
| 862 |
|
| 863 |
# ============================================
|
| 864 |
# FUNÇÃO PRINCIPAL
|
| 865 |
# ============================================
|
| 866 |
|
| 867 |
-
def gerar_post_interface(tema, nicho, estilo, formato, usar_cache,
|
| 868 |
descricao_imagem, gerar_img,
|
| 869 |
estilo_img_input, qualidade_img_input, filtro_img_input,
|
| 870 |
progress=gr.Progress()):
|
|
@@ -872,12 +904,12 @@ def gerar_post_interface(tema, nicho, estilo, formato, usar_cache, favorito_chec
|
|
| 872 |
Função principal unificada, com Cache, Analytics, Favoritos e Geração Avançada.
|
| 873 |
Retorna 7 valores para a UI.
|
| 874 |
"""
|
| 875 |
-
|
| 876 |
analytics_display = gerar_relatorio_analytics() # Carregar estado atual
|
| 877 |
-
|
| 878 |
progress(0, desc="🚀 Iniciando...")
|
| 879 |
time.sleep(0.3)
|
| 880 |
-
|
| 881 |
progress(0.1, desc="✅ Validando...")
|
| 882 |
if not tema or len(tema.strip()) < 3:
|
| 883 |
status_final = criar_alerta('error', '⚠️ Digite um tema válido!')
|
|
@@ -889,57 +921,57 @@ def gerar_post_interface(tema, nicho, estilo, formato, usar_cache, favorito_chec
|
|
| 889 |
if usar_cache:
|
| 890 |
progress(0.2, desc="🔍 Buscando no cache...")
|
| 891 |
texto, imagem = buscar_no_cache(cache_key)
|
| 892 |
-
|
| 893 |
if texto:
|
| 894 |
print("✅ Cache hit!")
|
| 895 |
progress(1.0, desc="🎉 Encontrado no cache!")
|
| 896 |
status_final = criar_alerta('success', '🎉 Post carregado do cache!')
|
| 897 |
-
|
| 898 |
-
atualizar_analytics(nicho, estilo, len(texto.split()), (imagem is not None), cache_hit=True, favorito=
|
| 899 |
analytics_display = gerar_relatorio_analytics() # Recarregar
|
| 900 |
-
|
| 901 |
palavras = len(texto.split())
|
| 902 |
caracteres = len(texto)
|
| 903 |
hashtags = texto.count('#')
|
| 904 |
-
|
| 905 |
history_entry = {
|
| 906 |
"Data/Hora": datetime.now(ZoneInfo("America/Bahia")).strftime("%Y-%m-%d %H:%M:%S"),
|
| 907 |
"Tema": tema, "Nicho": nicho, "Estilo": estilo, "Formato": formato,
|
| 908 |
"Texto": texto,
|
| 909 |
"Status": "Carregado do Cache",
|
| 910 |
-
"Favorito":
|
| 911 |
}
|
| 912 |
atualizar_historico(history_entry)
|
| 913 |
-
|
| 914 |
return (texto, imagem, status_final, palavras, caracteres, hashtags, analytics_display)
|
| 915 |
-
|
| 916 |
print("Cache miss ou cache desativado.")
|
| 917 |
progress(0.3, desc="🤖 Gerando texto (Llama 3.1)...")
|
| 918 |
-
|
| 919 |
# 2. Gerar Texto
|
| 920 |
-
texto = gerar_texto(tema, nicho, estilo, formato)
|
| 921 |
-
|
| 922 |
if texto.startswith("❌"):
|
| 923 |
status_final = criar_alerta('error', f'{texto}')
|
| 924 |
return (texto, None, status_final, 0, 0, 0, analytics_display)
|
| 925 |
-
|
| 926 |
progress(0.5, desc="✅ Texto pronto!")
|
| 927 |
time.sleep(0.5)
|
| 928 |
-
|
| 929 |
# 3. Gerar Imagem
|
| 930 |
imagem = None
|
| 931 |
status_imagem = ""
|
| 932 |
if gerar_img:
|
| 933 |
descricao_pt = descricao_imagem or f"{tema} imagem"
|
| 934 |
-
|
| 935 |
(imagem, status_imagem) = gerar_imagem_robusta(
|
| 936 |
-
descricao_pt,
|
| 937 |
-
estilo_img_input,
|
| 938 |
-
qualidade_img_input,
|
| 939 |
-
filtro_img_input,
|
| 940 |
progress
|
| 941 |
)
|
| 942 |
-
|
| 943 |
if imagem:
|
| 944 |
status_final = criar_alerta('success', f'🎉 Post completo gerado! ({status_imagem})')
|
| 945 |
else:
|
|
@@ -947,16 +979,16 @@ def gerar_post_interface(tema, nicho, estilo, formato, usar_cache, favorito_chec
|
|
| 947 |
else:
|
| 948 |
progress(0.7, desc="⏭️ Pulando geração de imagem...")
|
| 949 |
status_final = criar_alerta('success', '✅ Texto gerado (sem imagem)!')
|
| 950 |
-
|
| 951 |
time.sleep(0.5)
|
| 952 |
-
|
| 953 |
# 4. Estatísticas
|
| 954 |
progress(0.9, desc="📊 Calculando estatísticas...")
|
| 955 |
palavras = len(texto.split())
|
| 956 |
caracteres = len(texto)
|
| 957 |
hashtags = texto.count('#')
|
| 958 |
time.sleep(0.3)
|
| 959 |
-
|
| 960 |
# 5. Salvar no Cache
|
| 961 |
if usar_cache:
|
| 962 |
progress(0.95, desc="💾 Salvando no cache...")
|
|
@@ -966,23 +998,23 @@ def gerar_post_interface(tema, nicho, estilo, formato, usar_cache, favorito_chec
|
|
| 966 |
"imagem_path": imagem_path_cache
|
| 967 |
}
|
| 968 |
salvar_no_cache(cache_key, cache_data)
|
| 969 |
-
|
| 970 |
# 6. Atualizar Histórico (Firestore)
|
| 971 |
history_entry = {
|
| 972 |
"Data/Hora": datetime.now(ZoneInfo("America/Bahia")).strftime("%Y-%m-%d %H:%M:%S"),
|
| 973 |
"Tema": tema, "Nicho": nicho, "Estilo": estilo, "Formato": formato,
|
| 974 |
"Texto": texto, # Salva o texto completo
|
| 975 |
"Status": status_imagem or "Texto Gerado",
|
| 976 |
-
"Favorito":
|
| 977 |
}
|
| 978 |
atualizar_historico(history_entry)
|
| 979 |
-
|
| 980 |
# 7. Atualizar Analytics (Firestore)
|
| 981 |
-
atualizar_analytics(nicho, estilo, palavras, (imagem is not None), cache_hit=False, favorito=
|
| 982 |
analytics_display = gerar_relatorio_analytics() # Recarregar
|
| 983 |
-
|
| 984 |
progress(1.0, desc="🎉 Pronto!")
|
| 985 |
-
|
| 986 |
return (texto, imagem, status_final, palavras, caracteres, hashtags, analytics_display)
|
| 987 |
|
| 988 |
|
|
@@ -1006,27 +1038,27 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1006 |
# 🚀 Gerador de Posts e Assistente de Mídias Sociais (Versão Completa)
|
| 1007 |
### Powered by Hugging Face, Gradio, Llama 3.1 e Firebase
|
| 1008 |
""")
|
| 1009 |
-
|
| 1010 |
with gr.Tabs() as main_tabs: # Adicionado 'as main_tabs' para controle
|
| 1011 |
with gr.TabItem("✨ Gerar Post", id=0):
|
| 1012 |
with gr.Row():
|
| 1013 |
with gr.Column(scale=1):
|
| 1014 |
gr.Markdown("### ⚙️ 1. Configurações do Texto")
|
| 1015 |
-
|
| 1016 |
nicho_input = gr.Dropdown(
|
| 1017 |
choices=NICHOS_DISPONIVEIS,
|
| 1018 |
label="Escolha um nicho (para o texto)",
|
| 1019 |
value=NICHOS_DISPONIVEIS[0],
|
| 1020 |
interactive=True
|
| 1021 |
)
|
| 1022 |
-
|
| 1023 |
estilo_input = gr.Radio(
|
| 1024 |
choices=ESTILOS_DISPONIVEIS,
|
| 1025 |
label="Escolha um estilo (para o texto)",
|
| 1026 |
value=ESTILOS_DISPONIVEIS[0],
|
| 1027 |
interactive=True
|
| 1028 |
)
|
| 1029 |
-
|
| 1030 |
tema_input = gr.Textbox(
|
| 1031 |
label="Tema do Post",
|
| 1032 |
placeholder="Ex: Transforme seu corpo, transforme sua vida"
|
|
@@ -1038,25 +1070,25 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1038 |
value=list(FORMATO_CONFIGS.keys())[0],
|
| 1039 |
interactive=True
|
| 1040 |
)
|
| 1041 |
-
|
| 1042 |
usar_cache_checkbox = gr.Checkbox(
|
| 1043 |
label="Usar Cache? (Acelera posts repetidos)",
|
| 1044 |
value=True
|
| 1045 |
)
|
| 1046 |
-
|
| 1047 |
gr.Markdown("### 🎨 2. Configurações da Imagem (Opcional)")
|
| 1048 |
-
|
| 1049 |
gerar_img_checkbox = gr.Checkbox(
|
| 1050 |
label="Gerar imagem para o post?",
|
| 1051 |
value=False
|
| 1052 |
)
|
| 1053 |
-
|
| 1054 |
descricao_img_input = gr.Textbox(
|
| 1055 |
label="Descrição da imagem (em Português)",
|
| 1056 |
placeholder="Ex: Pessoa correndo ao nascer do sol",
|
| 1057 |
visible=False
|
| 1058 |
)
|
| 1059 |
-
|
| 1060 |
estilo_img_input = gr.Dropdown(
|
| 1061 |
label="Estilo da Imagem",
|
| 1062 |
choices=list(ESTILOS_DE_IMAGEM.keys()),
|
|
@@ -1064,7 +1096,7 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1064 |
visible=False,
|
| 1065 |
interactive=True
|
| 1066 |
)
|
| 1067 |
-
|
| 1068 |
qualidade_img_input = gr.Radio(
|
| 1069 |
label="Qualidade da Imagem (Velocidade vs Qualidade)",
|
| 1070 |
choices=["Rápida", "Balanceada", "Alta"],
|
|
@@ -1072,7 +1104,7 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1072 |
visible=False,
|
| 1073 |
interactive=True
|
| 1074 |
)
|
| 1075 |
-
|
| 1076 |
filtro_img_input = gr.Dropdown(
|
| 1077 |
label="Filtro da Imagem",
|
| 1078 |
choices=list(FILTROS_IMAGEM.keys()),
|
|
@@ -1080,7 +1112,7 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1080 |
visible=False,
|
| 1081 |
interactive=True
|
| 1082 |
)
|
| 1083 |
-
|
| 1084 |
def toggle_descricao_img(gerar):
|
| 1085 |
return (
|
| 1086 |
gr.Textbox(visible=gerar),
|
|
@@ -1088,41 +1120,41 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1088 |
gr.Radio(visible=gerar),
|
| 1089 |
gr.Dropdown(visible=gerar)
|
| 1090 |
)
|
| 1091 |
-
|
| 1092 |
gerar_img_checkbox.change(
|
| 1093 |
toggle_descricao_img,
|
| 1094 |
inputs=[gerar_img_checkbox],
|
| 1095 |
outputs=[descricao_img_input, estilo_img_input, qualidade_img_input, filtro_img_input]
|
| 1096 |
)
|
| 1097 |
-
|
| 1098 |
favorito_checkbox = gr.Checkbox(label="⭐ Favoritar este post?", value=False)
|
| 1099 |
-
|
| 1100 |
gerar_btn = gr.Button("✨ Gerar Post", variant="primary")
|
| 1101 |
-
|
| 1102 |
with gr.Column(scale=1):
|
| 1103 |
gr.Markdown("### 📋 3. Resultado")
|
| 1104 |
-
|
| 1105 |
status_output = gr.HTML(
|
| 1106 |
label="Status",
|
| 1107 |
value=criar_alerta('info', 'Pronto para gerar!')
|
| 1108 |
)
|
| 1109 |
-
|
| 1110 |
texto_output = gr.Textbox(
|
| 1111 |
label="Texto Gerado",
|
| 1112 |
lines=10,
|
| 1113 |
interactive=True, # Alterado para True para carregar do histórico
|
| 1114 |
show_copy_button=True
|
| 1115 |
)
|
| 1116 |
-
|
| 1117 |
with gr.Row():
|
| 1118 |
copiar_btn = gr.Button("📋 Copiar Texto", variant="secondary")
|
| 1119 |
limpar_btn = gr.Button("🧹 Limpar Tudo", variant="stop")
|
| 1120 |
-
|
| 1121 |
imagem_output = gr.Image(
|
| 1122 |
label="Imagem Gerada",
|
| 1123 |
type="pil"
|
| 1124 |
)
|
| 1125 |
-
|
| 1126 |
gr.Markdown("### 📥 4. Download")
|
| 1127 |
download_btn = gr.Button(
|
| 1128 |
"Baixar Post Completo (Imagem + Texto)",
|
|
@@ -1132,7 +1164,7 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1132 |
label="Clique para baixar",
|
| 1133 |
visible=True
|
| 1134 |
)
|
| 1135 |
-
|
| 1136 |
gr.Markdown("### 📊 Estatísticas do Texto")
|
| 1137 |
with gr.Row():
|
| 1138 |
palavras_output = gr.Number(label="Palavras", value=0, interactive=False)
|
|
@@ -1140,7 +1172,7 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1140 |
hashtags_output = gr.Number(label="Hashtags", value=0, interactive=False)
|
| 1141 |
|
| 1142 |
gr.Markdown("### 💡 Experimente estes exemplos:")
|
| 1143 |
-
|
| 1144 |
gr.Examples(
|
| 1145 |
examples=[
|
| 1146 |
[NICHOS_DISPONIVEIS[2], ESTILOS_DISPONIVEIS[0], "Frases marcantes de pessoas importantes", "Instagram (Post)"],
|
|
@@ -1151,68 +1183,96 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1151 |
inputs=[nicho_input, estilo_input, tema_input, formato_input],
|
| 1152 |
outputs=[texto_output, imagem_output, status_output]
|
| 1153 |
)
|
| 1154 |
-
|
| 1155 |
with gr.TabItem("💬 Chatbot Assistente", id=1):
|
| 1156 |
gr.Markdown("### 🤖 Assistente Virtual")
|
| 1157 |
gr.Markdown("Faça perguntas sobre mídias sociais, IA, peça ideias rápidas ou qualquer outro tópico.")
|
| 1158 |
|
| 1159 |
chatbot_para_interface = gr.Chatbot(
|
| 1160 |
height=500,
|
| 1161 |
-
type="messages"
|
| 1162 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1163 |
|
| 1164 |
-
gr.
|
| 1165 |
-
fn=responder_chat,
|
| 1166 |
-
title="Assistente Virtual",
|
| 1167 |
-
description="Converse com o Llama 3.1 para obter ajuda e insights.",
|
| 1168 |
examples=[
|
| 1169 |
"O que é um 'gancho' para Instagram?",
|
| 1170 |
"Me dê 3 ideias de post para um nicho de 'Fitness'",
|
| 1171 |
"Qual a diferença entre um post para Instagram e um para LinkedIn?"
|
| 1172 |
],
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1176 |
)
|
| 1177 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1178 |
with gr.TabItem("📚 Histórico de Posts", id=2):
|
| 1179 |
gr.Markdown("### 🔍 Buscar e Filtrar Histórico")
|
| 1180 |
gr.Markdown("Carregue posts antigos clicando em uma linha da tabela.")
|
| 1181 |
|
| 1182 |
with gr.Row():
|
| 1183 |
busca_query_input = gr.Textbox(
|
| 1184 |
-
label="Buscar por Tema/Texto",
|
| 1185 |
-
placeholder="Digite para buscar...",
|
| 1186 |
scale=3,
|
| 1187 |
interactive=True
|
| 1188 |
)
|
| 1189 |
filtro_nicho_hist = gr.Dropdown(
|
| 1190 |
-
label="Nicho",
|
| 1191 |
-
choices=["Todos"] + NICHOS_DISPONIVEIS,
|
| 1192 |
value="Todos",
|
| 1193 |
interactive=True
|
| 1194 |
)
|
| 1195 |
with gr.Row():
|
| 1196 |
filtro_estilo_hist = gr.Dropdown(
|
| 1197 |
-
label="Estilo",
|
| 1198 |
-
choices=["Todos"] + ESTILOS_DISPONIVEIS,
|
| 1199 |
value="Todos",
|
| 1200 |
interactive=True
|
| 1201 |
)
|
| 1202 |
filtro_formato_hist = gr.Dropdown(
|
| 1203 |
-
label="Formato",
|
| 1204 |
-
choices=["Todos"] + list(FORMATO_CONFIGS.keys()),
|
| 1205 |
value="Todos",
|
| 1206 |
interactive=True
|
| 1207 |
)
|
| 1208 |
filtro_favoritos_hist = gr.Checkbox(
|
| 1209 |
-
label="⭐ Apenas Favoritos",
|
| 1210 |
value=False,
|
| 1211 |
interactive=True
|
| 1212 |
)
|
| 1213 |
-
|
| 1214 |
buscar_hist_btn = gr.Button("Buscar", variant="primary")
|
| 1215 |
-
|
| 1216 |
historico_display = gr.Dataframe(
|
| 1217 |
headers=["⭐", "Data/Hora", "Tema", "Nicho", "Estilo", "Formato", "Texto (Preview)", "Status"],
|
| 1218 |
interactive=True, # Habilitado para seleção
|
|
@@ -1222,11 +1282,11 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1222 |
with gr.TabItem("📊 Analytics", id=3):
|
| 1223 |
gr.Markdown("### Análise de Uso da Ferramenta")
|
| 1224 |
gr.Markdown("Estes dados são salvos no Firestore e agregam o uso de todos os usuários.")
|
| 1225 |
-
|
| 1226 |
analytics_display = gr.Markdown(
|
| 1227 |
value=gerar_relatorio_analytics()
|
| 1228 |
)
|
| 1229 |
-
|
| 1230 |
with gr.Row():
|
| 1231 |
gerar_relatorio_btn = gr.Button("Atualizar Relatório", variant="secondary")
|
| 1232 |
resetar_analytics_btn = gr.Button("Resetar Analytics (CUIDADO)", variant="stop")
|
|
@@ -1270,10 +1330,10 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1270 |
**Como funciona:**
|
| 1271 |
1. **Gerar Post:** Você define o tema, nicho, estilo e **formato** do *texto*.
|
| 1272 |
2. **Imagem (Opcional):** Você ativa, descreve a imagem e seleciona *Estilo*, *Qualidade* e *Filtro*.
|
| 1273 |
-
3. O sistema otimiza o prompt, traduz para inglês e usa o sistema de *fallback* de modelos (
|
| 1274 |
4. **Download:** Após a geração, você pode clicar em "Baixar Post Completo" para salvar um PNG com a imagem e o texto formatado.
|
| 1275 |
5. **Chatbot:** Você pode conversar diretamente com a IA na aba 'Chatbot Assistente' para tirar dúvidas.
|
| 1276 |
-
6. **Histórico & Analytics:** Os posts gerados são salvos no Firestore
|
| 1277 |
|
| 1278 |
**Desenvolvido por:** Wilder Paz
|
| 1279 |
""")
|
|
@@ -1287,22 +1347,22 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1287 |
# ============================================
|
| 1288 |
# CONECTAR EVENTOS
|
| 1289 |
# ============================================
|
| 1290 |
-
|
| 1291 |
# Lista de inputs para o botão Gerar
|
| 1292 |
gerar_inputs = [
|
| 1293 |
-
tema_input, nicho_input, estilo_input,
|
| 1294 |
formato_input, usar_cache_checkbox, favorito_checkbox,
|
| 1295 |
descricao_img_input, gerar_img_checkbox,
|
| 1296 |
estilo_img_input, qualidade_img_input, filtro_img_input
|
| 1297 |
]
|
| 1298 |
-
|
| 1299 |
# Lista de outputs do botão Gerar
|
| 1300 |
gerar_outputs = [
|
| 1301 |
-
texto_output, imagem_output, status_output,
|
| 1302 |
palavras_output, caracteres_output, hashtags_output,
|
| 1303 |
analytics_display
|
| 1304 |
]
|
| 1305 |
-
|
| 1306 |
# Botão principal
|
| 1307 |
click_event = gerar_btn.click(
|
| 1308 |
fn=gerar_post_interface,
|
|
@@ -1310,20 +1370,20 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1310 |
outputs=gerar_outputs,
|
| 1311 |
show_progress="full"
|
| 1312 |
)
|
| 1313 |
-
|
| 1314 |
# Botão copiar
|
| 1315 |
copiar_btn.click(
|
| 1316 |
fn=copiar_feedback,
|
| 1317 |
inputs=[texto_output],
|
| 1318 |
outputs=[status_output]
|
| 1319 |
)
|
| 1320 |
-
|
| 1321 |
# Lista de outputs para o botão Limpar
|
| 1322 |
limpar_outputs = [
|
| 1323 |
# Aba Gerador
|
| 1324 |
-
texto_output, imagem_output, status_output,
|
| 1325 |
palavras_output, caracteres_output, hashtags_output,
|
| 1326 |
-
formato_input,
|
| 1327 |
estilo_img_input, qualidade_img_input, filtro_img_input,
|
| 1328 |
download_output,
|
| 1329 |
usar_cache_checkbox,
|
|
@@ -1336,46 +1396,46 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1336 |
filtro_formato_hist,
|
| 1337 |
filtro_favoritos_hist
|
| 1338 |
]
|
| 1339 |
-
|
| 1340 |
# Botão limpar
|
| 1341 |
limpar_btn.click(
|
| 1342 |
fn=limpar_tudo,
|
| 1343 |
inputs=[],
|
| 1344 |
outputs=limpar_outputs
|
| 1345 |
)
|
| 1346 |
-
|
| 1347 |
# Botão de Download
|
| 1348 |
download_btn.click(
|
| 1349 |
fn=preparar_download,
|
| 1350 |
inputs=[texto_output, imagem_output, tema_input],
|
| 1351 |
outputs=[download_output]
|
| 1352 |
)
|
| 1353 |
-
|
| 1354 |
# --- Eventos da Aba Histórico ---
|
| 1355 |
-
|
| 1356 |
# Lista de inputs para os filtros de histórico
|
| 1357 |
hist_filter_inputs = [
|
| 1358 |
-
busca_query_input,
|
| 1359 |
-
filtro_nicho_hist,
|
| 1360 |
-
filtro_estilo_hist,
|
| 1361 |
-
filtro_formato_hist,
|
| 1362 |
filtro_favoritos_hist
|
| 1363 |
]
|
| 1364 |
-
|
| 1365 |
# Botão de buscar no histórico
|
| 1366 |
buscar_hist_btn.click(
|
| 1367 |
fn=filtrar_historico_local,
|
| 1368 |
inputs=hist_filter_inputs,
|
| 1369 |
outputs=[historico_display]
|
| 1370 |
)
|
| 1371 |
-
|
| 1372 |
# Atualizar o histórico (mantendo filtros) após gerar um novo post
|
| 1373 |
click_event.then(
|
| 1374 |
fn=recarregar_e_formatar_historico,
|
| 1375 |
inputs=hist_filter_inputs,
|
| 1376 |
outputs=[historico_display]
|
| 1377 |
)
|
| 1378 |
-
|
| 1379 |
# Clicar em uma linha do histórico para carregar
|
| 1380 |
historico_display.select(
|
| 1381 |
fn=carregar_post_do_historico,
|
|
@@ -1392,22 +1452,44 @@ with gr.Blocks(theme=custom_theme, title="Gerador de Posts e Chatbot (Completo)"
|
|
| 1392 |
],
|
| 1393 |
show_progress="minimal"
|
| 1394 |
)
|
| 1395 |
-
|
| 1396 |
# --- Eventos da Aba Analytics ---
|
| 1397 |
-
|
| 1398 |
gerar_relatorio_btn.click(
|
| 1399 |
fn=gerar_relatorio_analytics,
|
| 1400 |
inputs=None,
|
| 1401 |
outputs=[analytics_display]
|
| 1402 |
)
|
| 1403 |
-
|
| 1404 |
resetar_analytics_btn.click(
|
| 1405 |
fn=resetar_analytics,
|
| 1406 |
inputs=None,
|
| 1407 |
-
outputs=[analytics_display]
|
|
|
|
| 1408 |
)
|
| 1409 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1410 |
# Lançar aplicação
|
| 1411 |
if __name__ == "__main__":
|
| 1412 |
-
demo.launch()
|
| 1413 |
-
|
|
|
|
| 139 |
def _inicializar_firestore():
|
| 140 |
"""
|
| 141 |
Inicializa o Firebase Admin SDK usando as credenciais
|
| 142 |
+
armazenadas nos Secrets do Hugging Face Spaces ou Colab.
|
| 143 |
"""
|
| 144 |
global db, analytics
|
| 145 |
+
|
| 146 |
+
# Preferir userdata no Colab
|
| 147 |
secret_name = "FIREBASE_SERVICE_ACCOUNT_JSON"
|
| 148 |
+
secret_json_string = userdata.get(secret_name)
|
| 149 |
+
|
| 150 |
+
if not secret_json_string:
|
| 151 |
+
# Fallback para ambiente Hugging Face Spaces
|
| 152 |
+
secret_json_string = os.environ.get(secret_name)
|
| 153 |
+
|
| 154 |
|
| 155 |
if not secret_json_string:
|
| 156 |
print(f"❌ Erro de Configuração do Firebase: Secret '{secret_name}' não encontrado.")
|
|
|
|
| 258 |
def atualizar_analytics(nicho, estilo, palavras, imagem_gerada, cache_hit, favorito):
|
| 259 |
"""Atualiza as métricas de analytics (agora salva no Firestore)."""
|
| 260 |
global analytics
|
| 261 |
+
|
| 262 |
analytics['total_posts'] = analytics.get('total_posts', 0) + 1
|
| 263 |
analytics['total_palavras'] = analytics.get('total_palavras', 0) + palavras
|
| 264 |
+
|
| 265 |
if imagem_gerada:
|
| 266 |
analytics['total_imagens'] = analytics.get('total_imagens', 0) + 1
|
| 267 |
+
|
| 268 |
if cache_hit:
|
| 269 |
analytics['cache_hits'] = analytics.get('cache_hits', 0) + 1
|
| 270 |
else:
|
| 271 |
analytics['cache_misses'] = analytics.get('cache_misses', 0) + 1
|
| 272 |
+
|
| 273 |
if favorito:
|
| 274 |
analytics['total_favoritos'] = analytics.get('total_favoritos', 0) + 1
|
| 275 |
|
|
|
|
| 277 |
nicho_counts = analytics.get('posts_por_nicho', {})
|
| 278 |
nicho_counts[nicho] = nicho_counts.get(nicho, 0) + 1
|
| 279 |
analytics['posts_por_nicho'] = nicho_counts
|
| 280 |
+
|
| 281 |
estilo_counts = analytics.get('posts_por_estilo', {})
|
| 282 |
estilo_counts[estilo] = estilo_counts.get(estilo, 0) + 1
|
| 283 |
analytics['posts_por_estilo'] = estilo_counts
|
| 284 |
+
|
| 285 |
# Salvar no Firestore
|
| 286 |
_salvar_analytics_firestore()
|
| 287 |
|
|
|
|
| 290 |
global analytics
|
| 291 |
if not analytics or 'status' in analytics or analytics.get("total_posts", 0) == 0:
|
| 292 |
return "📊 Nenhum post gerado ainda."
|
| 293 |
+
|
| 294 |
# Ordenar os dicionários por valor (mais usados primeiro)
|
| 295 |
posts_por_nicho_sorted = dict(sorted(analytics.get('posts_por_nicho', {}).items(), key=lambda item: item[1], reverse=True))
|
| 296 |
posts_por_estilo_sorted = dict(sorted(analytics.get('posts_por_estilo', {}).items(), key=lambda item: item[1], reverse=True))
|
| 297 |
|
| 298 |
total_reqs = analytics.get('cache_hits', 0) + analytics.get('cache_misses', 0)
|
| 299 |
taxa_cache_hit = (analytics.get('cache_hits', 0) / total_reqs * 100) if total_reqs > 0 else 0
|
| 300 |
+
|
| 301 |
nicho_top = max(analytics["posts_por_nicho"].items(), key=lambda x: x[1]) if analytics.get("posts_por_nicho") else ("N/A", 0)
|
| 302 |
estilo_top = max(analytics["posts_por_estilo"].items(), key=lambda x: x[1]) if analytics.get("posts_por_estilo") else ("N/A", 0)
|
| 303 |
|
|
|
|
| 363 |
try:
|
| 364 |
with open(cache_file, 'r', encoding='utf-8') as f:
|
| 365 |
data = json.load(f)
|
| 366 |
+
|
| 367 |
texto = data.get("texto")
|
| 368 |
imagem_path = data.get("imagem_path")
|
| 369 |
imagem = None
|
| 370 |
+
|
| 371 |
if imagem_path:
|
| 372 |
img_cache_file = CACHE_DIR / imagem_path
|
| 373 |
if img_cache_file.exists():
|
| 374 |
imagem = Image.open(img_cache_file)
|
| 375 |
else:
|
| 376 |
return None, None
|
| 377 |
+
|
| 378 |
return texto, imagem
|
| 379 |
except Exception as e:
|
| 380 |
print(f"Erro ao ler cache {key}: {e}")
|
|
|
|
| 385 |
"""Salva a imagem PIL no diretório de cache e retorna o nome do arquivo."""
|
| 386 |
if not imagem_pil:
|
| 387 |
return None
|
| 388 |
+
|
| 389 |
try:
|
| 390 |
imagem_path = f"{key}_img.png"
|
| 391 |
imagem_pil.save(CACHE_DIR / imagem_path)
|
|
|
|
| 443 |
return criar_alerta('success', '✅ Texto copiado!')
|
| 444 |
return criar_alerta('warning', '⚠️ Nada para copiar')
|
| 445 |
|
| 446 |
+
def print_like_dislike(x: gr.LikeData):
|
| 447 |
+
"""Função de callback para o evento 'like' do chatbot."""
|
| 448 |
+
print(f"Mensagem {x.index} foi marcada como: {x.value}, Liked: {x.liked}")
|
| 449 |
+
|
| 450 |
def limpar_tudo():
|
| 451 |
"""Limpa todos os inputs da UI, incluindo filtros de histórico."""
|
| 452 |
analytics_data = gerar_relatorio_analytics() # Gerar relatório limpo
|
|
|
|
| 516 |
def filtrar_historico_local(query, nicho, estilo, formato, favoritos_apenas):
|
| 517 |
"""Filtra a lista global `post_history` com base nos inputs da UI."""
|
| 518 |
global post_history
|
| 519 |
+
|
| 520 |
# Começa com a lista completa
|
| 521 |
resultados = post_history
|
| 522 |
+
|
| 523 |
# Filtro de busca por texto
|
| 524 |
if query:
|
| 525 |
query_lower = query.lower()
|
| 526 |
resultados = [
|
| 527 |
+
post for post in resultados
|
| 528 |
if query_lower in post.get("Tema", "").lower() or query_lower in post.get("Texto", "").lower()
|
| 529 |
]
|
| 530 |
+
|
| 531 |
# Filtro de Nicho
|
| 532 |
if nicho != "Todos":
|
| 533 |
resultados = [post for post in resultados if post.get("Nicho") == nicho]
|
| 534 |
+
|
| 535 |
# Filtro de Estilo
|
| 536 |
if estilo != "Todos":
|
| 537 |
resultados = [post for post in resultados if post.get("Estilo") == estilo]
|
| 538 |
+
|
| 539 |
# Filtro de Formato
|
| 540 |
if formato != "Todos":
|
| 541 |
resultados = [post for post in resultados if post.get("Formato") == formato]
|
| 542 |
+
|
| 543 |
# Filtro de Favoritos
|
| 544 |
if favoritos_apenas:
|
| 545 |
resultados = [post for post in resultados if post.get("Favorito") == True]
|
| 546 |
+
|
| 547 |
# Formata para o Dataframe
|
| 548 |
return _formatar_historico_para_df(resultados)
|
| 549 |
|
|
|
|
| 556 |
try:
|
| 557 |
# Pega o post correspondente da lista global
|
| 558 |
post_selecionado = post_history[evt.index]
|
| 559 |
+
|
| 560 |
# Extrai os dados
|
| 561 |
tema = post_selecionado.get("Tema", "")
|
| 562 |
nicho = post_selecionado.get("Nicho", NICHOS_DISPONIVEIS[0])
|
|
|
|
| 564 |
formato = post_selecionado.get("Formato", list(FORMATO_CONFIGS.keys())[0])
|
| 565 |
favorito = post_selecionado.get("Favorito", False)
|
| 566 |
texto = post_selecionado.get("Texto", "")
|
| 567 |
+
|
| 568 |
# Feedback para o usuário
|
| 569 |
status_alerta = criar_alerta('info', '✅ Post carregado do histórico! A imagem deve ser gerada novamente se desejado.')
|
| 570 |
+
|
| 571 |
# Retorna os valores para a UI, incluindo a mudança de aba
|
| 572 |
return (
|
| 573 |
gr.Tabs(selected=0), # Muda para a primeira aba (Gerar Post)
|
|
|
|
| 595 |
"""
|
| 596 |
Gera texto usando API do Hugging Face com base no formato escolhido.
|
| 597 |
"""
|
| 598 |
+
|
| 599 |
if not HUGGINGFACE_API_KEY:
|
| 600 |
return "❌ Erro de Configuração: API Key não está definida."
|
| 601 |
|
| 602 |
config = FORMATO_CONFIGS.get(formato, FORMATO_CONFIGS["Instagram (Post)"])
|
| 603 |
+
|
| 604 |
url = f"{BASE_URL}/chat/completions"
|
| 605 |
|
| 606 |
payload = {
|
|
|
|
| 656 |
|
| 657 |
try:
|
| 658 |
response = requests.post(url, headers=headers, json=payload, timeout=30)
|
| 659 |
+
|
| 660 |
if response.status_code == 200:
|
| 661 |
resultado = response.json()
|
| 662 |
if resultado and isinstance(resultado, list) and 'translation_text' in resultado[0]:
|
|
|
|
| 672 |
|
| 673 |
def otimizar_prompt_imagem(descricao_pt, estilo_escolhido, filtro_escolhido):
|
| 674 |
"""Combina as escolhas do usuário em um prompt otimizado (em Português)."""
|
| 675 |
+
|
| 676 |
estilo = ESTILOS_DE_IMAGEM.get(estilo_escolhido, ESTILOS_DE_IMAGEM["Nenhum (Automático)"])
|
| 677 |
filtro = FILTROS_IMAGEM.get(filtro_escolhido, FILTROS_IMAGEM["Nenhum"])
|
| 678 |
+
|
| 679 |
prompt_final = f"{descricao_pt}, {estilo}, {filtro}, best quality, 4k"
|
| 680 |
+
|
| 681 |
prompt_final = prompt_final.replace(", ,", ",").replace(", ,", ",")
|
| 682 |
return prompt_final
|
| 683 |
|
|
|
|
| 690 |
Gera imagem com sistema robusto de fallback e controle de qualidade.
|
| 691 |
Retorna: (PIL.Image, str_mensagem_status)
|
| 692 |
"""
|
| 693 |
+
|
| 694 |
# 1. Configs de Qualidade
|
| 695 |
configs_qualidade = {
|
| 696 |
"Rápida": {
|
|
|
|
| 707 |
}
|
| 708 |
}
|
| 709 |
config = configs_qualidade.get(qualidade, configs_qualidade["Balanceada"])
|
| 710 |
+
|
| 711 |
# 2. Otimizar e Traduzir Prompt
|
| 712 |
if progress: progress(0.55, desc="🌍 Otimizando e traduzindo prompt...")
|
| 713 |
prompt_otimizado_pt = otimizar_prompt_imagem(descricao_pt, estilo_escolhido, filtro_escolhido)
|
|
|
|
| 720 |
if progress:
|
| 721 |
prog_val = 0.6 + (i * 0.1) # Ajustar progresso
|
| 722 |
progress(prog_val, desc=f"🎨 Tentando {modelo_config['nome']}...")
|
| 723 |
+
|
| 724 |
print(f"Tentando gerar imagem com {modelo_config['nome']}...")
|
| 725 |
|
| 726 |
client = InferenceClient(api_key=HUGGINGFACE_API_KEY)
|
| 727 |
+
|
| 728 |
imagem = client.text_to_image(
|
| 729 |
prompt=prompt_final_en,
|
| 730 |
model=modelo_config['id'],
|
| 731 |
negative_prompt=negative_prompt,
|
| 732 |
num_inference_steps=config['steps']
|
| 733 |
)
|
| 734 |
+
|
| 735 |
print(f"✅ Imagem gerada com {modelo_config['nome']}")
|
| 736 |
mensagem = f"✅ Imagem gerada com {modelo_config['nome']}"
|
| 737 |
+
|
| 738 |
return (imagem, mensagem) # Retorna (PIL.Image, str)
|
| 739 |
|
| 740 |
except Exception as e:
|
| 741 |
print(f"❌ Falha com {modelo_config['nome']}: {str(e)}")
|
| 742 |
+
|
| 743 |
if i < len(config['modelos']) - 1:
|
| 744 |
print(f"⏭️ Tentando próximo modelo...")
|
| 745 |
continue
|
|
|
|
| 754 |
# FUNÇÃO DO CHATBOT
|
| 755 |
# ============================================
|
| 756 |
def responder_chat(message, chat_history):
|
| 757 |
+
"""
|
| 758 |
+
Função principal de lógica do chatbot. Recebe a nova mensagem e o histórico,
|
| 759 |
+
retorna a string de resposta da IA.
|
| 760 |
+
"""
|
| 761 |
if not HUGGINGFACE_API_KEY:
|
| 762 |
return "❌ Erro de Configuração: API Key não está definida."
|
| 763 |
|
|
|
|
| 765 |
|
| 766 |
system_prompt = "Você é um assistente virtual prestativo e amigável, especializado em marketing de mídias sociais e criação de conteúdo, mas pode responder sobre qualquer tópico. Seja direto e útil."
|
| 767 |
|
| 768 |
+
# Constrói o payload de mensagens
|
| 769 |
messages = [{"role": "system", "content": system_prompt}]
|
| 770 |
+
# Adiciona o histórico existente
|
| 771 |
messages.extend(chat_history)
|
| 772 |
+
# Adiciona a nova mensagem do usuário
|
| 773 |
messages.append({"role": "user", "content": message})
|
| 774 |
|
| 775 |
payload = {
|
|
|
|
| 796 |
except Exception as e:
|
| 797 |
return f"❌ {interpretar_erro_api(str(e))}"
|
| 798 |
|
| 799 |
+
def chatbot_respond(message, chat_history):
|
| 800 |
+
"""
|
| 801 |
+
Função wrapper para a UI do Gradio.
|
| 802 |
+
Recebe a mensagem e o histórico, chama a lógica do bot,
|
| 803 |
+
e retorna o histórico atualizado.
|
| 804 |
+
"""
|
| 805 |
+
# 1. Adiciona a mensagem do usuário ao histórico
|
| 806 |
+
chat_history.append({"role": "user", "content": message})
|
| 807 |
+
# 2. Obtém a resposta do bot (string)
|
| 808 |
+
bot_response_str = responder_chat(message, chat_history)
|
| 809 |
+
# 3. Adiciona a resposta do bot ao histórico
|
| 810 |
+
chat_history.append({"role": "assistant", "content": bot_response_str})
|
| 811 |
+
# 4. Retorna a caixa de texto vazia e o histórico atualizado
|
| 812 |
+
return "", chat_history
|
| 813 |
+
|
| 814 |
# ============================================
|
| 815 |
# FUNÇÕES DE DOWNLOAD
|
| 816 |
# ============================================
|
|
|
|
| 823 |
if not imagem_pil or not texto:
|
| 824 |
print("Erro: Texto ou Imagem ausente para criar post.")
|
| 825 |
return None
|
| 826 |
+
|
| 827 |
try:
|
| 828 |
LARGURA_POST = 1080
|
| 829 |
ALTURA_IMAGEM = 1080
|
| 830 |
PADDING = 60
|
| 831 |
COR_FUNDO = (255, 255, 255)
|
| 832 |
COR_TEXTO = (0, 0, 0)
|
| 833 |
+
|
| 834 |
try:
|
| 835 |
fonte_texto = ImageFont.truetype("DejaVuSans.ttf", size=42)
|
| 836 |
fonte_titulo = ImageFont.truetype("DejaVuSans-Bold.ttf", size=55)
|
|
|
|
| 840 |
fonte_titulo = ImageFont.load_default()
|
| 841 |
|
| 842 |
imagem_quadrada = imagem_pil.resize((LARGURA_POST, ALTURA_IMAGEM), Image.Resampling.LANCZOS)
|
| 843 |
+
|
| 844 |
linhas_titulo = textwrap.wrap(tema.upper(), width=40)
|
| 845 |
linhas_texto = textwrap.wrap(texto, width=50)
|
| 846 |
+
|
| 847 |
+
altura_titulo = len(linhas_titulo) * 60
|
| 848 |
altura_texto = len(linhas_texto) * 45
|
| 849 |
+
altura_total_texto = altura_titulo + 20 + altura_texto + (PADDING * 2)
|
| 850 |
+
|
| 851 |
altura_total = ALTURA_IMAGEM + altura_total_texto
|
| 852 |
post_completo = Image.new('RGB', (LARGURA_POST, int(altura_total)), COR_FUNDO)
|
| 853 |
+
|
| 854 |
post_completo.paste(imagem_quadrada, (0, 0))
|
| 855 |
+
|
| 856 |
draw = ImageDraw.Draw(post_completo)
|
| 857 |
pos_y = ALTURA_IMAGEM + PADDING
|
| 858 |
+
|
| 859 |
for linha in linhas_titulo:
|
| 860 |
largura_linha = draw.textlength(linha, font=fonte_titulo)
|
| 861 |
pos_x_titulo = (LARGURA_POST - largura_linha) / 2
|
| 862 |
draw.text((pos_x_titulo, pos_y), linha, font=fonte_titulo, fill=COR_TEXTO)
|
| 863 |
pos_y += 60
|
| 864 |
+
|
| 865 |
pos_y += 20
|
| 866 |
+
|
| 867 |
for linha in linhas_texto:
|
| 868 |
draw.text((PADDING, pos_y), linha, font=fonte_texto, fill=COR_TEXTO)
|
| 869 |
pos_y += 45
|
|
|
|
| 872 |
post_completo.save(f, 'PNG')
|
| 873 |
print(f"Arquivo temporário salvo em: {f.name}")
|
| 874 |
return f.name
|
| 875 |
+
|
| 876 |
except Exception as e:
|
| 877 |
print(f"❌ Erro ao criar post completo: {e}")
|
| 878 |
return None
|
|
|
|
| 889 |
|
| 890 |
if caminho_arquivo:
|
| 891 |
return caminho_arquivo
|
| 892 |
+
|
| 893 |
return None
|
| 894 |
|
| 895 |
# ============================================
|
| 896 |
# FUNÇÃO PRINCIPAL
|
| 897 |
# ============================================
|
| 898 |
|
| 899 |
+
def gerar_post_interface(tema, nicho, estilo, formato, usar_cache, favorito_checkbox_value, # Capture the boolean value
|
| 900 |
descricao_imagem, gerar_img,
|
| 901 |
estilo_img_input, qualidade_img_input, filtro_img_input,
|
| 902 |
progress=gr.Progress()):
|
|
|
|
| 904 |
Função principal unificada, com Cache, Analytics, Favoritos e Geração Avançada.
|
| 905 |
Retorna 7 valores para a UI.
|
| 906 |
"""
|
| 907 |
+
|
| 908 |
analytics_display = gerar_relatorio_analytics() # Carregar estado atual
|
| 909 |
+
|
| 910 |
progress(0, desc="🚀 Iniciando...")
|
| 911 |
time.sleep(0.3)
|
| 912 |
+
|
| 913 |
progress(0.1, desc="✅ Validando...")
|
| 914 |
if not tema or len(tema.strip()) < 3:
|
| 915 |
status_final = criar_alerta('error', '⚠️ Digite um tema válido!')
|
|
|
|
| 921 |
if usar_cache:
|
| 922 |
progress(0.2, desc="🔍 Buscando no cache...")
|
| 923 |
texto, imagem = buscar_no_cache(cache_key)
|
| 924 |
+
|
| 925 |
if texto:
|
| 926 |
print("✅ Cache hit!")
|
| 927 |
progress(1.0, desc="🎉 Encontrado no cache!")
|
| 928 |
status_final = criar_alerta('success', '🎉 Post carregado do cache!')
|
| 929 |
+
|
| 930 |
+
atualizar_analytics(nicho, estilo, len(texto.split()), (imagem is not None), cache_hit=True, favorito=favorito_checkbox_value) # Pass the boolean value
|
| 931 |
analytics_display = gerar_relatorio_analytics() # Recarregar
|
| 932 |
+
|
| 933 |
palavras = len(texto.split())
|
| 934 |
caracteres = len(texto)
|
| 935 |
hashtags = texto.count('#')
|
| 936 |
+
|
| 937 |
history_entry = {
|
| 938 |
"Data/Hora": datetime.now(ZoneInfo("America/Bahia")).strftime("%Y-%m-%d %H:%M:%S"),
|
| 939 |
"Tema": tema, "Nicho": nicho, "Estilo": estilo, "Formato": formato,
|
| 940 |
"Texto": texto,
|
| 941 |
"Status": "Carregado do Cache",
|
| 942 |
+
"Favorito": favorito_checkbox_value # Pass the boolean value
|
| 943 |
}
|
| 944 |
atualizar_historico(history_entry)
|
| 945 |
+
|
| 946 |
return (texto, imagem, status_final, palavras, caracteres, hashtags, analytics_display)
|
| 947 |
+
|
| 948 |
print("Cache miss ou cache desativado.")
|
| 949 |
progress(0.3, desc="🤖 Gerando texto (Llama 3.1)...")
|
| 950 |
+
|
| 951 |
# 2. Gerar Texto
|
| 952 |
+
texto = gerar_texto(tema, nicho, estilo, formato)
|
| 953 |
+
|
| 954 |
if texto.startswith("❌"):
|
| 955 |
status_final = criar_alerta('error', f'{texto}')
|
| 956 |
return (texto, None, status_final, 0, 0, 0, analytics_display)
|
| 957 |
+
|
| 958 |
progress(0.5, desc="✅ Texto pronto!")
|
| 959 |
time.sleep(0.5)
|
| 960 |
+
|
| 961 |
# 3. Gerar Imagem
|
| 962 |
imagem = None
|
| 963 |
status_imagem = ""
|
| 964 |
if gerar_img:
|
| 965 |
descricao_pt = descricao_imagem or f"{tema} imagem"
|
| 966 |
+
|
| 967 |
(imagem, status_imagem) = gerar_imagem_robusta(
|
| 968 |
+
descricao_pt,
|
| 969 |
+
estilo_img_input,
|
| 970 |
+
qualidade_img_input,
|
| 971 |
+
filtro_img_input,
|
| 972 |
progress
|
| 973 |
)
|
| 974 |
+
|
| 975 |
if imagem:
|
| 976 |
status_final = criar_alerta('success', f'🎉 Post completo gerado! ({status_imagem})')
|
| 977 |
else:
|
|
|
|
| 979 |
else:
|
| 980 |
progress(0.7, desc="⏭️ Pulando geração de imagem...")
|
| 981 |
status_final = criar_alerta('success', '✅ Texto gerado (sem imagem)!')
|
| 982 |
+
|
| 983 |
time.sleep(0.5)
|
| 984 |
+
|
| 985 |
# 4. Estatísticas
|
| 986 |
progress(0.9, desc="📊 Calculando estatísticas...")
|
| 987 |
palavras = len(texto.split())
|
| 988 |
caracteres = len(texto)
|
| 989 |
hashtags = texto.count('#')
|
| 990 |
time.sleep(0.3)
|
| 991 |
+
|
| 992 |
# 5. Salvar no Cache
|
| 993 |
if usar_cache:
|
| 994 |
progress(0.95, desc="💾 Salvando no cache...")
|
|
|
|
| 998 |
"imagem_path": imagem_path_cache
|
| 999 |
}
|
| 1000 |
salvar_no_cache(cache_key, cache_data)
|
| 1001 |
+
|
| 1002 |
# 6. Atualizar Histórico (Firestore)
|
| 1003 |
history_entry = {
|
| 1004 |
"Data/Hora": datetime.now(ZoneInfo("America/Bahia")).strftime("%Y-%m-%d %H:%M:%S"),
|
| 1005 |
"Tema": tema, "Nicho": nicho, "Estilo": estilo, "Formato": formato,
|
| 1006 |
"Texto": texto, # Salva o texto completo
|
| 1007 |
"Status": status_imagem or "Texto Gerado",
|
| 1008 |
+
"Favorito": favorito_checkbox_value # Pass the boolean value
|
| 1009 |
}
|
| 1010 |
atualizar_historico(history_entry)
|
| 1011 |
+
|
| 1012 |
# 7. Atualizar Analytics (Firestore)
|
| 1013 |
+
atualizar_analytics(nicho, estilo, palavras, (imagem is not None), cache_hit=False, favorito=favorito_checkbox_value) # Pass the boolean value
|
| 1014 |
analytics_display = gerar_relatorio_analytics() # Recarregar
|
| 1015 |
+
|
| 1016 |
progress(1.0, desc="🎉 Pronto!")
|
| 1017 |
+
|
| 1018 |
return (texto, imagem, status_final, palavras, caracteres, hashtags, analytics_display)
|
| 1019 |
|
| 1020 |
|
|
|
|
| 1038 |
# 🚀 Gerador de Posts e Assistente de Mídias Sociais (Versão Completa)
|
| 1039 |
### Powered by Hugging Face, Gradio, Llama 3.1 e Firebase
|
| 1040 |
""")
|
| 1041 |
+
|
| 1042 |
with gr.Tabs() as main_tabs: # Adicionado 'as main_tabs' para controle
|
| 1043 |
with gr.TabItem("✨ Gerar Post", id=0):
|
| 1044 |
with gr.Row():
|
| 1045 |
with gr.Column(scale=1):
|
| 1046 |
gr.Markdown("### ⚙️ 1. Configurações do Texto")
|
| 1047 |
+
|
| 1048 |
nicho_input = gr.Dropdown(
|
| 1049 |
choices=NICHOS_DISPONIVEIS,
|
| 1050 |
label="Escolha um nicho (para o texto)",
|
| 1051 |
value=NICHOS_DISPONIVEIS[0],
|
| 1052 |
interactive=True
|
| 1053 |
)
|
| 1054 |
+
|
| 1055 |
estilo_input = gr.Radio(
|
| 1056 |
choices=ESTILOS_DISPONIVEIS,
|
| 1057 |
label="Escolha um estilo (para o texto)",
|
| 1058 |
value=ESTILOS_DISPONIVEIS[0],
|
| 1059 |
interactive=True
|
| 1060 |
)
|
| 1061 |
+
|
| 1062 |
tema_input = gr.Textbox(
|
| 1063 |
label="Tema do Post",
|
| 1064 |
placeholder="Ex: Transforme seu corpo, transforme sua vida"
|
|
|
|
| 1070 |
value=list(FORMATO_CONFIGS.keys())[0],
|
| 1071 |
interactive=True
|
| 1072 |
)
|
| 1073 |
+
|
| 1074 |
usar_cache_checkbox = gr.Checkbox(
|
| 1075 |
label="Usar Cache? (Acelera posts repetidos)",
|
| 1076 |
value=True
|
| 1077 |
)
|
| 1078 |
+
|
| 1079 |
gr.Markdown("### 🎨 2. Configurações da Imagem (Opcional)")
|
| 1080 |
+
|
| 1081 |
gerar_img_checkbox = gr.Checkbox(
|
| 1082 |
label="Gerar imagem para o post?",
|
| 1083 |
value=False
|
| 1084 |
)
|
| 1085 |
+
|
| 1086 |
descricao_img_input = gr.Textbox(
|
| 1087 |
label="Descrição da imagem (em Português)",
|
| 1088 |
placeholder="Ex: Pessoa correndo ao nascer do sol",
|
| 1089 |
visible=False
|
| 1090 |
)
|
| 1091 |
+
|
| 1092 |
estilo_img_input = gr.Dropdown(
|
| 1093 |
label="Estilo da Imagem",
|
| 1094 |
choices=list(ESTILOS_DE_IMAGEM.keys()),
|
|
|
|
| 1096 |
visible=False,
|
| 1097 |
interactive=True
|
| 1098 |
)
|
| 1099 |
+
|
| 1100 |
qualidade_img_input = gr.Radio(
|
| 1101 |
label="Qualidade da Imagem (Velocidade vs Qualidade)",
|
| 1102 |
choices=["Rápida", "Balanceada", "Alta"],
|
|
|
|
| 1104 |
visible=False,
|
| 1105 |
interactive=True
|
| 1106 |
)
|
| 1107 |
+
|
| 1108 |
filtro_img_input = gr.Dropdown(
|
| 1109 |
label="Filtro da Imagem",
|
| 1110 |
choices=list(FILTROS_IMAGEM.keys()),
|
|
|
|
| 1112 |
visible=False,
|
| 1113 |
interactive=True
|
| 1114 |
)
|
| 1115 |
+
|
| 1116 |
def toggle_descricao_img(gerar):
|
| 1117 |
return (
|
| 1118 |
gr.Textbox(visible=gerar),
|
|
|
|
| 1120 |
gr.Radio(visible=gerar),
|
| 1121 |
gr.Dropdown(visible=gerar)
|
| 1122 |
)
|
| 1123 |
+
|
| 1124 |
gerar_img_checkbox.change(
|
| 1125 |
toggle_descricao_img,
|
| 1126 |
inputs=[gerar_img_checkbox],
|
| 1127 |
outputs=[descricao_img_input, estilo_img_input, qualidade_img_input, filtro_img_input]
|
| 1128 |
)
|
| 1129 |
+
|
| 1130 |
favorito_checkbox = gr.Checkbox(label="⭐ Favoritar este post?", value=False)
|
| 1131 |
+
|
| 1132 |
gerar_btn = gr.Button("✨ Gerar Post", variant="primary")
|
| 1133 |
+
|
| 1134 |
with gr.Column(scale=1):
|
| 1135 |
gr.Markdown("### 📋 3. Resultado")
|
| 1136 |
+
|
| 1137 |
status_output = gr.HTML(
|
| 1138 |
label="Status",
|
| 1139 |
value=criar_alerta('info', 'Pronto para gerar!')
|
| 1140 |
)
|
| 1141 |
+
|
| 1142 |
texto_output = gr.Textbox(
|
| 1143 |
label="Texto Gerado",
|
| 1144 |
lines=10,
|
| 1145 |
interactive=True, # Alterado para True para carregar do histórico
|
| 1146 |
show_copy_button=True
|
| 1147 |
)
|
| 1148 |
+
|
| 1149 |
with gr.Row():
|
| 1150 |
copiar_btn = gr.Button("📋 Copiar Texto", variant="secondary")
|
| 1151 |
limpar_btn = gr.Button("🧹 Limpar Tudo", variant="stop")
|
| 1152 |
+
|
| 1153 |
imagem_output = gr.Image(
|
| 1154 |
label="Imagem Gerada",
|
| 1155 |
type="pil"
|
| 1156 |
)
|
| 1157 |
+
|
| 1158 |
gr.Markdown("### 📥 4. Download")
|
| 1159 |
download_btn = gr.Button(
|
| 1160 |
"Baixar Post Completo (Imagem + Texto)",
|
|
|
|
| 1164 |
label="Clique para baixar",
|
| 1165 |
visible=True
|
| 1166 |
)
|
| 1167 |
+
|
| 1168 |
gr.Markdown("### 📊 Estatísticas do Texto")
|
| 1169 |
with gr.Row():
|
| 1170 |
palavras_output = gr.Number(label="Palavras", value=0, interactive=False)
|
|
|
|
| 1172 |
hashtags_output = gr.Number(label="Hashtags", value=0, interactive=False)
|
| 1173 |
|
| 1174 |
gr.Markdown("### 💡 Experimente estes exemplos:")
|
| 1175 |
+
|
| 1176 |
gr.Examples(
|
| 1177 |
examples=[
|
| 1178 |
[NICHOS_DISPONIVEIS[2], ESTILOS_DISPONIVEIS[0], "Frases marcantes de pessoas importantes", "Instagram (Post)"],
|
|
|
|
| 1183 |
inputs=[nicho_input, estilo_input, tema_input, formato_input],
|
| 1184 |
outputs=[texto_output, imagem_output, status_output]
|
| 1185 |
)
|
| 1186 |
+
|
| 1187 |
with gr.TabItem("💬 Chatbot Assistente", id=1):
|
| 1188 |
gr.Markdown("### 🤖 Assistente Virtual")
|
| 1189 |
gr.Markdown("Faça perguntas sobre mídias sociais, IA, peça ideias rápidas ou qualquer outro tópico.")
|
| 1190 |
|
| 1191 |
chatbot_para_interface = gr.Chatbot(
|
| 1192 |
height=500,
|
| 1193 |
+
type="messages"
|
| 1194 |
)
|
| 1195 |
+
|
| 1196 |
+
with gr.Column():
|
| 1197 |
+
with gr.Row():
|
| 1198 |
+
chat_input = gr.Textbox(
|
| 1199 |
+
show_label=False,
|
| 1200 |
+
placeholder="Digite sua mensagem aqui...",
|
| 1201 |
+
scale=7
|
| 1202 |
+
)
|
| 1203 |
+
submit_btn = gr.Button("Enviar", variant="primary", scale=1)
|
| 1204 |
+
|
| 1205 |
+
clear_btn = gr.ClearButton(
|
| 1206 |
+
[chat_input, chatbot_para_interface],
|
| 1207 |
+
value="🧹 Limpar Chat"
|
| 1208 |
+
)
|
| 1209 |
|
| 1210 |
+
gr.Examples(
|
|
|
|
|
|
|
|
|
|
| 1211 |
examples=[
|
| 1212 |
"O que é um 'gancho' para Instagram?",
|
| 1213 |
"Me dê 3 ideias de post para um nicho de 'Fitness'",
|
| 1214 |
"Qual a diferença entre um post para Instagram e um para LinkedIn?"
|
| 1215 |
],
|
| 1216 |
+
inputs=[chat_input]
|
| 1217 |
+
)
|
| 1218 |
+
|
| 1219 |
+
# Conectar eventos do chatbot
|
| 1220 |
+
submit_btn.click(
|
| 1221 |
+
fn=chatbot_respond,
|
| 1222 |
+
inputs=[chat_input, chatbot_para_interface],
|
| 1223 |
+
outputs=[chat_input, chatbot_para_interface]
|
| 1224 |
+
)
|
| 1225 |
+
|
| 1226 |
+
chat_input.submit(
|
| 1227 |
+
fn=chatbot_respond,
|
| 1228 |
+
inputs=[chat_input, chatbot_para_interface],
|
| 1229 |
+
outputs=[chat_input, chatbot_para_interface]
|
| 1230 |
)
|
| 1231 |
|
| 1232 |
+
chatbot_para_interface.like(
|
| 1233 |
+
fn=print_like_dislike,
|
| 1234 |
+
inputs=None,
|
| 1235 |
+
outputs=None
|
| 1236 |
+
)
|
| 1237 |
+
|
| 1238 |
with gr.TabItem("📚 Histórico de Posts", id=2):
|
| 1239 |
gr.Markdown("### 🔍 Buscar e Filtrar Histórico")
|
| 1240 |
gr.Markdown("Carregue posts antigos clicando em uma linha da tabela.")
|
| 1241 |
|
| 1242 |
with gr.Row():
|
| 1243 |
busca_query_input = gr.Textbox(
|
| 1244 |
+
label="Buscar por Tema/Texto",
|
| 1245 |
+
placeholder="Digite para buscar...",
|
| 1246 |
scale=3,
|
| 1247 |
interactive=True
|
| 1248 |
)
|
| 1249 |
filtro_nicho_hist = gr.Dropdown(
|
| 1250 |
+
label="Nicho",
|
| 1251 |
+
choices=["Todos"] + NICHOS_DISPONIVEIS,
|
| 1252 |
value="Todos",
|
| 1253 |
interactive=True
|
| 1254 |
)
|
| 1255 |
with gr.Row():
|
| 1256 |
filtro_estilo_hist = gr.Dropdown(
|
| 1257 |
+
label="Estilo",
|
| 1258 |
+
choices=["Todos"] + ESTILOS_DISPONIVEIS,
|
| 1259 |
value="Todos",
|
| 1260 |
interactive=True
|
| 1261 |
)
|
| 1262 |
filtro_formato_hist = gr.Dropdown(
|
| 1263 |
+
label="Formato",
|
| 1264 |
+
choices=["Todos"] + list(FORMATO_CONFIGS.keys()),
|
| 1265 |
value="Todos",
|
| 1266 |
interactive=True
|
| 1267 |
)
|
| 1268 |
filtro_favoritos_hist = gr.Checkbox(
|
| 1269 |
+
label="⭐ Apenas Favoritos",
|
| 1270 |
value=False,
|
| 1271 |
interactive=True
|
| 1272 |
)
|
| 1273 |
+
|
| 1274 |
buscar_hist_btn = gr.Button("Buscar", variant="primary")
|
| 1275 |
+
|
| 1276 |
historico_display = gr.Dataframe(
|
| 1277 |
headers=["⭐", "Data/Hora", "Tema", "Nicho", "Estilo", "Formato", "Texto (Preview)", "Status"],
|
| 1278 |
interactive=True, # Habilitado para seleção
|
|
|
|
| 1282 |
with gr.TabItem("📊 Analytics", id=3):
|
| 1283 |
gr.Markdown("### Análise de Uso da Ferramenta")
|
| 1284 |
gr.Markdown("Estes dados são salvos no Firestore e agregam o uso de todos os usuários.")
|
| 1285 |
+
|
| 1286 |
analytics_display = gr.Markdown(
|
| 1287 |
value=gerar_relatorio_analytics()
|
| 1288 |
)
|
| 1289 |
+
|
| 1290 |
with gr.Row():
|
| 1291 |
gerar_relatorio_btn = gr.Button("Atualizar Relatório", variant="secondary")
|
| 1292 |
resetar_analytics_btn = gr.Button("Resetar Analytics (CUIDADO)", variant="stop")
|
|
|
|
| 1330 |
**Como funciona:**
|
| 1331 |
1. **Gerar Post:** Você define o tema, nicho, estilo e **formato** do *texto*.
|
| 1332 |
2. **Imagem (Opcional):** Você ativa, descreve a imagem e seleciona *Estilo*, *Qualidade* e *Filtro*.
|
| 1333 |
+
3. O sistema otimiza o prompt, traduz para inglês e usa o sistema de *fallback* de modelos (based on *Qualidade*) para gerar a imagem.
|
| 1334 |
4. **Download:** Após a geração, você pode clicar em "Baixar Post Completo" para salvar um PNG com a imagem e o texto formatado.
|
| 1335 |
5. **Chatbot:** Você pode conversar diretamente com a IA na aba 'Chatbot Assistente' para tirar dúvidas.
|
| 1336 |
+
6. **Histórico & Analytics:** Os posts gerados são salvos no Firestore and as métricas de uso são atualizadas.
|
| 1337 |
|
| 1338 |
**Desenvolvido por:** Wilder Paz
|
| 1339 |
""")
|
|
|
|
| 1347 |
# ============================================
|
| 1348 |
# CONECTAR EVENTOS
|
| 1349 |
# ============================================
|
| 1350 |
+
|
| 1351 |
# Lista de inputs para o botão Gerar
|
| 1352 |
gerar_inputs = [
|
| 1353 |
+
tema_input, nicho_input, estilo_input,
|
| 1354 |
formato_input, usar_cache_checkbox, favorito_checkbox,
|
| 1355 |
descricao_img_input, gerar_img_checkbox,
|
| 1356 |
estilo_img_input, qualidade_img_input, filtro_img_input
|
| 1357 |
]
|
| 1358 |
+
|
| 1359 |
# Lista de outputs do botão Gerar
|
| 1360 |
gerar_outputs = [
|
| 1361 |
+
texto_output, imagem_output, status_output,
|
| 1362 |
palavras_output, caracteres_output, hashtags_output,
|
| 1363 |
analytics_display
|
| 1364 |
]
|
| 1365 |
+
|
| 1366 |
# Botão principal
|
| 1367 |
click_event = gerar_btn.click(
|
| 1368 |
fn=gerar_post_interface,
|
|
|
|
| 1370 |
outputs=gerar_outputs,
|
| 1371 |
show_progress="full"
|
| 1372 |
)
|
| 1373 |
+
|
| 1374 |
# Botão copiar
|
| 1375 |
copiar_btn.click(
|
| 1376 |
fn=copiar_feedback,
|
| 1377 |
inputs=[texto_output],
|
| 1378 |
outputs=[status_output]
|
| 1379 |
)
|
| 1380 |
+
|
| 1381 |
# Lista de outputs para o botão Limpar
|
| 1382 |
limpar_outputs = [
|
| 1383 |
# Aba Gerador
|
| 1384 |
+
texto_output, imagem_output, status_output,
|
| 1385 |
palavras_output, caracteres_output, hashtags_output,
|
| 1386 |
+
formato_input,
|
| 1387 |
estilo_img_input, qualidade_img_input, filtro_img_input,
|
| 1388 |
download_output,
|
| 1389 |
usar_cache_checkbox,
|
|
|
|
| 1396 |
filtro_formato_hist,
|
| 1397 |
filtro_favoritos_hist
|
| 1398 |
]
|
| 1399 |
+
|
| 1400 |
# Botão limpar
|
| 1401 |
limpar_btn.click(
|
| 1402 |
fn=limpar_tudo,
|
| 1403 |
inputs=[],
|
| 1404 |
outputs=limpar_outputs
|
| 1405 |
)
|
| 1406 |
+
|
| 1407 |
# Botão de Download
|
| 1408 |
download_btn.click(
|
| 1409 |
fn=preparar_download,
|
| 1410 |
inputs=[texto_output, imagem_output, tema_input],
|
| 1411 |
outputs=[download_output]
|
| 1412 |
)
|
| 1413 |
+
|
| 1414 |
# --- Eventos da Aba Histórico ---
|
| 1415 |
+
|
| 1416 |
# Lista de inputs para os filtros de histórico
|
| 1417 |
hist_filter_inputs = [
|
| 1418 |
+
busca_query_input,
|
| 1419 |
+
filtro_nicho_hist,
|
| 1420 |
+
filtro_estilo_hist,
|
| 1421 |
+
filtro_formato_hist,
|
| 1422 |
filtro_favoritos_hist
|
| 1423 |
]
|
| 1424 |
+
|
| 1425 |
# Botão de buscar no histórico
|
| 1426 |
buscar_hist_btn.click(
|
| 1427 |
fn=filtrar_historico_local,
|
| 1428 |
inputs=hist_filter_inputs,
|
| 1429 |
outputs=[historico_display]
|
| 1430 |
)
|
| 1431 |
+
|
| 1432 |
# Atualizar o histórico (mantendo filtros) após gerar um novo post
|
| 1433 |
click_event.then(
|
| 1434 |
fn=recarregar_e_formatar_historico,
|
| 1435 |
inputs=hist_filter_inputs,
|
| 1436 |
outputs=[historico_display]
|
| 1437 |
)
|
| 1438 |
+
|
| 1439 |
# Clicar em uma linha do histórico para carregar
|
| 1440 |
historico_display.select(
|
| 1441 |
fn=carregar_post_do_historico,
|
|
|
|
| 1452 |
],
|
| 1453 |
show_progress="minimal"
|
| 1454 |
)
|
| 1455 |
+
|
| 1456 |
# --- Eventos da Aba Analytics ---
|
| 1457 |
+
|
| 1458 |
gerar_relatorio_btn.click(
|
| 1459 |
fn=gerar_relatorio_analytics,
|
| 1460 |
inputs=None,
|
| 1461 |
outputs=[analytics_display]
|
| 1462 |
)
|
| 1463 |
+
|
| 1464 |
resetar_analytics_btn.click(
|
| 1465 |
fn=resetar_analytics,
|
| 1466 |
inputs=None,
|
| 1467 |
+
outputs=[analytics_display],
|
| 1468 |
+
js="() => confirm('Tem certeza que deseja resetar TODOS os dados de analytics e cache? Esta ação não pode ser desfeita.')"
|
| 1469 |
)
|
| 1470 |
|
| 1471 |
+
# Definir a função resetar_analytics
|
| 1472 |
+
# Renomeada para evitar conflito com a função que será chamada pelo evento
|
| 1473 |
+
def resetar_analytics():
|
| 1474 |
+
"""Reseta os dados de analytics no Firestore e localmente."""
|
| 1475 |
+
global analytics
|
| 1476 |
+
analytics = {
|
| 1477 |
+
"total_posts": 0,
|
| 1478 |
+
"posts_por_nicho": {},
|
| 1479 |
+
"posts_por_estilo": {},
|
| 1480 |
+
"total_palavras": 0,
|
| 1481 |
+
"total_imagens": 0,
|
| 1482 |
+
"cache_hits": 0,
|
| 1483 |
+
"cache_misses": 0,
|
| 1484 |
+
"total_favoritos": 0
|
| 1485 |
+
}
|
| 1486 |
+
_salvar_analytics_firestore()
|
| 1487 |
+
# Limpar cache local
|
| 1488 |
+
for f in CACHE_DIR.glob('*'):
|
| 1489 |
+
f.unlink()
|
| 1490 |
+
print("Analytics e Cache resetados.")
|
| 1491 |
+
return gerar_relatorio_analytics()
|
| 1492 |
+
|
| 1493 |
# Lançar aplicação
|
| 1494 |
if __name__ == "__main__":
|
| 1495 |
+
demo.launch()
|
|
|