wpbcpaz commited on
Commit
5f49c76
·
verified ·
1 Parent(s): 647bac7

Update app.py

Browse files

Melhorias na aba Chatbot Assistente

Files changed (1) hide show
  1. app.py +240 -158
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 = os.environ.get(secret_name)
 
 
 
 
 
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, favorito_checkbox,
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=favorito_checkbox)
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": favorito_checkbox
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": favorito_checkbox
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=favorito_checkbox)
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.ChatInterface(
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
- chatbot=chatbot_para_interface,
1174
- textbox=gr.Textbox(placeholder="Digite sua mensagem aqui...", scale=7),
1175
- submit_btn="Enviar"
 
 
 
 
 
 
 
 
 
 
 
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 (baseado na *Qualidade*) para gerar a imagem.
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 e as métricas de uso são atualizadas.
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()