Spaces:
Running
Running
File size: 15,003 Bytes
4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 4ffe0a9 bbbc107 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 |
import os
import logging
import json
import requests
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from google import genai
from google.genai import types
from datetime import datetime
from zoneinfo import ZoneInfo
import locale
import re
# Configurar logging
logger = logging.getLogger(__name__)
router = APIRouter()
class NewsRequest(BaseModel):
content: str
sources_url: str # URL do arquivo fontes.txt
class NewsResponse(BaseModel):
title: str
subhead: str
content: str
sources: list[str] # Lista de URLs/links utilizados
def get_brazilian_date_string():
"""
Retorna a data atual formatada em português brasileiro.
Implementa fallbacks robustos para diferentes sistemas operacionais.
"""
try:
# Tenta configurar o locale brasileiro
locale_variants = [
'pt_BR.UTF-8',
'pt_BR.utf8',
'pt_BR',
'Portuguese_Brazil.1252',
'Portuguese_Brazil',
'pt_BR.ISO8859-1',
]
locale_set = False
for loc in locale_variants:
try:
locale.setlocale(locale.LC_TIME, loc)
locale_set = True
break
except locale.Error:
continue
if not locale_set:
locale.setlocale(locale.LC_TIME, '')
now = datetime.now(ZoneInfo("America/Sao_Paulo"))
# Dicionários para tradução manual (fallback)
meses = {
1: 'janeiro', 2: 'fevereiro', 3: 'março', 4: 'abril',
5: 'maio', 6: 'junho', 7: 'julho', 8: 'agosto',
9: 'setembro', 10: 'outubro', 11: 'novembro', 12: 'dezembro'
}
dias_semana = {
0: 'segunda-feira', 1: 'terça-feira', 2: 'quarta-feira',
3: 'quinta-feira', 4: 'sexta-feira', 5: 'sábado', 6: 'domingo'
}
try:
if locale_set:
try:
date_string = now.strftime("%-d de %B de %Y (%A)")
except ValueError:
try:
date_string = now.strftime("%#d de %B de %Y (%A)")
except ValueError:
date_string = now.strftime("%d de %B de %Y (%A)")
if date_string.startswith('0'):
date_string = date_string[1:]
date_string = date_string.replace(date_string.split('(')[1].split(')')[0],
date_string.split('(')[1].split(')')[0].lower())
else:
dia = now.day
mes = meses[now.month]
ano = now.year
dia_semana = dias_semana[now.weekday()]
date_string = f"{dia} de {mes} de {ano} ({dia_semana})"
except Exception:
dia = now.day
mes = meses[now.month]
ano = now.year
dia_semana = dias_semana[now.weekday()]
date_string = f"{dia} de {mes} de {ano} ({dia_semana})"
return date_string
except Exception:
now = datetime.now(ZoneInfo("America/Sao_Paulo"))
date_string = now.strftime("%d de %B de %Y")
return date_string
def download_sources_file(url: str) -> str:
"""
Baixa o arquivo fontes.txt da URL fornecida.
"""
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
return response.text
except Exception as e:
logger.error(f"Erro ao baixar arquivo de fontes: {e}")
raise HTTPException(status_code=400, detail=f"Erro ao baixar arquivo de fontes: {str(e)}")
def extract_text_from_response(response):
"""
Extrai o texto da resposta de forma robusta.
"""
response_text = ""
if hasattr(response, 'text') and response.text:
return response.text
if hasattr(response, 'candidates') and response.candidates:
for candidate in response.candidates:
if not hasattr(candidate, 'content') or not candidate.content:
continue
content = candidate.content
if not hasattr(content, 'parts') or content.parts is None:
continue
try:
parts_list = list(content.parts) if content.parts else []
for part in parts_list:
if hasattr(part, 'text') and part.text:
response_text += part.text
except Exception:
continue
return response_text
def extract_sources_from_response(response):
"""
Extrai as fontes (URLs) do grounding metadata.
"""
sources = []
if not (hasattr(response, 'candidates') and response.candidates):
return sources
for candidate in response.candidates:
if not (hasattr(candidate, 'grounding_metadata') and candidate.grounding_metadata):
continue
grounding_metadata = candidate.grounding_metadata
if hasattr(grounding_metadata, 'grounding_chunks') and grounding_metadata.grounding_chunks:
for chunk in grounding_metadata.grounding_chunks:
try:
if (hasattr(chunk, 'web') and chunk.web and
hasattr(chunk.web, 'uri') and chunk.web.uri):
uri = chunk.web.uri
if uri and uri not in sources:
sources.append(uri)
except Exception:
continue
return sources
@router.post("/rewrite-news", response_model=NewsResponse)
async def rewrite_news(news: NewsRequest):
"""
Endpoint para reescrever notícias usando o modelo Gemini com arquivo de fontes.
"""
try:
# Verificar API key
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="API key não configurada")
# Baixar arquivo de fontes
sources_content = download_sources_file(news.sources_url)
client = genai.Client(api_key=api_key)
model = "gemini-2.5-pro"
# Obter data formatada
date_string = get_brazilian_date_string()
# Instruções do sistema
SYSTEM_INSTRUCTIONS = f"""
Você é um jornalista brasileiro, escrevendo para portais digitais. Sua missão é transformar notícias internacionais em matérias originais, atualizadas e envolventes para o público brasileiro.
ESTILO DE ESCRITA:
- Tom natural, direto e conversacional, sem frases feitas ou jargões jornalísticos.
- Varie estruturas e conectivos para evitar robotização. Ou seja, sem frases redundantes, exemplo: "destacando como a experiência pode ser um divisor de águas profissional", "reafirma a força criativa do país no cenário global", "A revelação contextualizou não apenas sua performance na dança, mas também". É para noticiar a notícia de fato, sempre buscando mais informações que se conectam, e não opiniões pessoais.
- Sem linguagem militante ou juízos morais: mantenha tom informativo e predominantemente conservador / aspectro de Direita que o jornal tem.
- Evite opiniões genéricas; foque em fatos e contexto
CONTEÚDO, ESTRUTURA E ABORDAGEM:
- Use a notícia-base como ponto de partida, não como roteiro. Avalie o fontes.txt por completo e extraia absolutamente todas as coisas relevantes para formentar a notícia. Não traga informações falsas ou inventadas. Na dúvida, não insira.
- Sempre que possível, inclua títulos de obras em Português do Brasil.
- Termine com uma informação concreta (data, lançamento, próximos passos), nunca com opinião genérica. Ou seja, a conclusão da noticia deve ser com mais noticia, sem redundância genérica e robótica.
- Otimize para leitura digital e SEO (parágrafos bem segmentados, palavras chaves, etc)
FORMATO:
<headline>título aqui</headline>
<subhead>subtítulo aqui</subhead>
<body>conteúdo aqui</body>
Use <strong> para destaques e <em> para títulos de obras ou citações.
TÍTULOS:
- Padrão brasileiro: só a primeira palavra em maiúscula (exceto nomes próprios e títulos de filmes/séries/obras, etc.)
- Seja claro, direto e específico... Se houver, não inclua spoilers no título, apenas no body.
O resultado deve soar como uma matéria escrita por um profissional experiente, não por IA. Seja preciso, atual e interessante. Sempre complete a notícia com acontecimentos que se ligam, sempre contextualize tudo para o leitor. A data de hoje é {date_string}
"""
# Exemplos (mantidos os mesmos do código original)
EXAMPLE_INPUT_1 = """
News base: Ed Helms revealed in an interview that he was nervous about his parents' reaction to the film The Hangover, but in the end everything worked out and her mother loved the movie. The article is out of date, more information is needed.
"""
EXAMPLE_OUTPUT_1 = """<headline>"Se Beber, Não Case!": Ed Helms, o Dr. Stuart, revela medo do que os pais iriam pensar, mas tudo deu certo</headline>
<subhead>Em uma carreira repleta de surpresas e sucesso internacional, o ator relembra o nervosismo que antecedeu a estreia da comédia que o tornou famoso.</subhead>
<body>
<p><strong>Ed Helms</strong> nunca escondeu o fato de que sua participação em <strong>Se Beber, Não Case!</strong> foi um choque cultural, especialmente para seus pais. Em uma entrevista recente ao podcast de <strong>Ted Danson</strong>, <em>Where Everybody Knows Your Name</em>, o ator falou sobre a ansiedade que sentiu ao imaginar a reação da família à comédia para maiores que o transformou em astro de cinema.</p>
<p>Helms, que foi criado em um lar sulista com valores socialmente conservadores, revelou que, embora o ambiente fosse politicamente progressista, algumas situações, como dentes arrancados, casamentos embriagados e até tigres no banheiro, eram muito diferentes do que seus pais consideravam apropriado. O ator brincou: <em>"Não foi pra isso que me criaram"</em>, fazendo alusão ao enredo caótico do filme de 2009. Ele acrescentou que, embora seus pais já tivessem assistido a algumas de suas performances em programas como <em>The Daily Show</em> e <em>The Office</em>, o que ajudou a criar certa tolerância, o filme ainda o deixava nervoso.</p>
<p>Estrelando sua primeira grande produção, Helms levou os pais para a estreia quando tinha 35 anos. No entanto, foi surpreendido ao ver sua mãe chorando quando as luzes se acenderam. <em>"Pensei: 'Pronto. Acabei de partir o coração da minha mãe'"</em>, recordou. O momento de tensão, porém, durou pouco: ela o tranquilizou dizendo que o filme havia sido hilário.</p>
<p><strong>Se Beber, Não Case!</strong>, dirigido por <strong>Todd Phillips</strong>, foi um sucesso comercial, arrecadando aproximadamente <strong>469 milhões de dólares</strong> em todo o mundo e se tornando a comédia para maiores de classificação indicativa de maior bilheteria até então. A popularidade do filme resultou em duas sequências, lançadas em 2011 e 2013, e consolidou o "bando de lobos" formado por <strong>Helms</strong>, <strong>Bradley Cooper</strong> e <strong>Zach Galifianakis</strong> como um dos times cômicos mais icônicos do cinema moderno.</p>
<p>Sobre a possibilidade de um quarto filme, <strong>Bradley Cooper</strong> afirmou em 2023 que toparia participar sem hesitar, principalmente pela chance de reencontrar colegas e diretor. Ainda assim, reconheceu que o projeto é improvável, já que <strong>Phillips</strong> está atualmente focado em empreendimentos de maior escala, como a série de filmes <em>Coringa</em>.</p>
</body>"""
# Configuração da ferramenta de pesquisa
grounding_tool = types.Tool(
google_search=types.GoogleSearch()
)
config = types.GenerateContentConfig(
system_instruction=SYSTEM_INSTRUCTIONS,
thinking_config=types.ThinkingConfig(
thinking_budget=-1,
),
tools=[grounding_tool],
response_mime_type="text/plain",
max_output_tokens=4096,
temperature=0.8,
)
# Conteúdo da conversa
contents = [
# Exemplo
types.Content(
role="user",
parts=[
types.Part.from_text(text=EXAMPLE_INPUT_1)
]
),
types.Content(
role="model",
parts=[
types.Part.from_text(text=EXAMPLE_OUTPUT_1)
]
),
# Notícia atual com arquivo de fontes
types.Content(
role="user",
parts=[
types.Part.from_text(text=f"News base: {news.content}. The article is out of date, more information is needed."),
types.Part.from_text(text=f"Fontes adicionais disponíveis:\n\n{sources_content}")
]
)
]
# Gerar conteúdo
response = client.models.generate_content(
model=model,
contents=contents,
config=config
)
# Extrair texto e fontes
response_text = extract_text_from_response(response)
sources = extract_sources_from_response(response)
# Verificar se o texto está vazio
if not response_text or response_text.strip() == "":
raise HTTPException(
status_code=500,
detail="Modelo não retornou conteúdo válido"
)
# Extração do título, subtítulo e conteúdo
title_match = re.search(r"<headline>(.*?)</headline>", response_text, re.DOTALL)
title = title_match.group(1).strip() if title_match else "Título não encontrado"
subhead_match = re.search(r"<subhead>(.*?)</subhead>", response_text, re.DOTALL)
subhead = subhead_match.group(1).strip() if subhead_match else "Subtítulo não encontrado"
body_match = re.search(r"<body>(.*?)</body>", response_text, re.DOTALL)
if body_match:
content = body_match.group(1).strip()
else:
body_start_match = re.search(r"<body>(.*)", response_text, re.DOTALL)
if body_start_match:
content = body_start_match.group(1).strip()
else:
content = "Conteúdo não encontrado"
return NewsResponse(title=title, subhead=subhead, content=content, sources=sources)
except HTTPException:
raise
except Exception as e:
logger.error(f"Erro na reescrita: {str(e)}")
raise HTTPException(status_code=500, detail=str(e)) |