habulaj commited on
Commit
3eb6514
·
verified ·
1 Parent(s): f911c2c

Create inference_createposter.py

Browse files
Files changed (1) hide show
  1. routers/inference_createposter.py +171 -0
routers/inference_createposter.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from fastapi import APIRouter, HTTPException
4
+ from pydantic import BaseModel
5
+ from google import genai
6
+ from google.genai import types
7
+
8
+ # Configurar logging
9
+ logger = logging.getLogger(__name__)
10
+
11
+ router = APIRouter()
12
+
13
+ class PosterRequest(BaseModel):
14
+ content: str
15
+
16
+ class PosterResponse(BaseModel):
17
+ result: dict
18
+
19
+ @router.post("/generate-poster", response_model=PosterResponse)
20
+ async def generate_poster(request: PosterRequest):
21
+ """
22
+ Endpoint para gerar posters automáticos (notícias ou carrosséis) usando o modelo Gemini.
23
+ """
24
+ try:
25
+ # Verificar API key
26
+ api_key = os.environ.get("GEMINI_API_KEY")
27
+ if not api_key:
28
+ raise HTTPException(status_code=500, detail="API key não configurada")
29
+
30
+ client = genai.Client(api_key=api_key)
31
+ model = "gemini-2.5-pro"
32
+
33
+ # System instructions
34
+ SYSTEM_INSTRUCTIONS = """
35
+ Você é uma IA especializada em gerar **posters automáticos** (notícias ou carrosséis de slides) a partir de texto fornecido.
36
+
37
+ 🔎 Regras de interpretação
38
+ 1. Analise o texto recebido.
39
+ 2. Se for **uma notícia simples** → gere **um único JSON** correspondente ao endpoint `/cover/news`.
40
+ 3. Se o texto for longo, didático, explicativo, ou parecer adequado para **um carrossel de slides** → gere **um único JSON** contendo:
41
+ * O objeto da **capa** (`/create/cover/image`)
42
+ * Uma lista `slides` com cada slide (`/create/image`).
43
+
44
+ 📌 Estrutura esperada do JSON
45
+
46
+ 1. Notícia simples → `/cover/news`
47
+
48
+ ```json
49
+ {
50
+ "endpoint": "/cover/news",
51
+ "params": {
52
+ "image_url": "URL_DA_IMAGEM",
53
+ "headline": "Título da notícia",
54
+ "text_position": "bottom"
55
+ }
56
+ }
57
+ ```
58
+
59
+ 2. Carrossel de slides → capa + slides
60
+
61
+ ```json
62
+ {
63
+ "cover": {
64
+ "endpoint": "/create/cover/image",
65
+ "params": {
66
+ "image_url": "URL_DA_IMAGEM_CAPA",
67
+ "title": "Título principal do carrossel",
68
+ "title_position": "bottom"
69
+ }
70
+ },
71
+ "slides": [
72
+ {
73
+ "endpoint": "/create/image",
74
+ "params": {
75
+ "image_url": "URL_DA_IMAGEM_SLIDE_1",
76
+ "text": "Texto do primeiro slide (aceita <strong> e <em>)",
77
+ "text_position": "bottom",
78
+ "citation": null,
79
+ "citation_direction": "bottom"
80
+ }
81
+ },
82
+ {
83
+ "endpoint": "/create/image",
84
+ "params": {
85
+ "image_url": "URL_DA_IMAGEM_SLIDE_2",
86
+ "text": "Texto do segundo slide",
87
+ "text_position": "bottom",
88
+ "citation": "Citação de apoio (opcional)",
89
+ "citation_direction": "text-top"
90
+ }
91
+ }
92
+ ]
93
+ }
94
+ ```
95
+
96
+ 🎯 Restrições importantes
97
+ * Sempre escolha apenas **um formato**:
98
+ * **Notícia simples** → objeto único.
99
+ * **Slides** → objeto com `cover` + `slides`.
100
+ * O campo `image_url` deve sempre estar presente (use um valor genérico se não fornecido).
101
+ * Use `text_position = bottom` como padrão.
102
+ * Inclua `citation` apenas se houver frase curta de apoio, destaque ou fala atribuída.
103
+
104
+ IMPORTANTE: Retorne apenas o JSON válido, sem explicações adicionais.
105
+ """
106
+
107
+ # Configuração da geração
108
+ config = types.GenerateContentConfig(
109
+ system_instruction=SYSTEM_INSTRUCTIONS,
110
+ response_mime_type="application/json",
111
+ max_output_tokens=8000,
112
+ temperature=0.7,
113
+ )
114
+
115
+ # Conteúdo da conversa
116
+ contents = [
117
+ types.Content(
118
+ role="user",
119
+ parts=[types.Part.from_text(text=request.content)]
120
+ )
121
+ ]
122
+
123
+ # Gerar conteúdo
124
+ response = client.models.generate_content(
125
+ model=model,
126
+ contents=contents,
127
+ config=config
128
+ )
129
+
130
+ logger.info("Resposta do modelo recebida com sucesso")
131
+
132
+ # Extrair texto da resposta
133
+ response_text = ""
134
+ if hasattr(response, 'text') and response.text:
135
+ response_text = response.text
136
+ elif hasattr(response, 'candidates') and response.candidates:
137
+ for candidate in response.candidates:
138
+ if hasattr(candidate, 'content') and candidate.content:
139
+ if hasattr(candidate.content, 'parts') and candidate.content.parts:
140
+ for part in candidate.content.parts:
141
+ if hasattr(part, 'text') and part.text:
142
+ response_text += part.text
143
+
144
+ if not response_text or response_text.strip() == "":
145
+ logger.error("Resposta do modelo está vazia")
146
+ raise HTTPException(
147
+ status_code=500,
148
+ detail="Modelo não retornou conteúdo válido"
149
+ )
150
+
151
+ # Parse do JSON
152
+ import json
153
+ try:
154
+ result_json = json.loads(response_text)
155
+ except json.JSONDecodeError as e:
156
+ logger.error(f"Erro ao fazer parse do JSON: {e}")
157
+ logger.error(f"Resposta recebida: {response_text}")
158
+ raise HTTPException(
159
+ status_code=500,
160
+ detail=f"Resposta do modelo não é um JSON válido: {str(e)}"
161
+ )
162
+
163
+ logger.info("Processamento concluído com sucesso")
164
+
165
+ return PosterResponse(result=result_json)
166
+
167
+ except HTTPException:
168
+ raise
169
+ except Exception as e:
170
+ logger.error(f"Erro na geração do poster: {str(e)}")
171
+ raise HTTPException(status_code=500, detail=str(e))