GuXSs commited on
Commit
0e8a23e
·
verified ·
1 Parent(s): 9d2d92e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +133 -158
app.py CHANGED
@@ -1,9 +1,9 @@
 
1
  import os
2
  import secrets
3
  import logging
4
  import asyncio
5
  import html
6
-
7
  from dataclasses import dataclass
8
  from typing import Any, Optional, Tuple
9
 
@@ -11,6 +11,9 @@ import gradio as gr
11
  from transformers import pipeline
12
  from dotenv import load_dotenv
13
  from pydantic import BaseModel
 
 
 
14
 
15
  # ----------------- Configuration & Models -----------------
16
  load_dotenv()
@@ -38,25 +41,20 @@ class APIResponse(BaseModel):
38
  error: Optional[str] = None
39
 
40
 
41
- # ----------------- Enhanced Logger -----------------
42
  def setup_logger() -> logging.Logger:
43
  cfg = Config()
44
  log_level = getattr(logging, cfg.LOG_LEVEL.upper(), logging.INFO)
45
-
46
  logger = logging.getLogger("gemma_saas")
47
  if not logger.handlers:
48
  logger.setLevel(log_level)
49
  formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
50
-
51
- file_handler = logging.FileHandler("gemma_saas.log")
52
- file_handler.setFormatter(formatter)
53
-
54
- stream_handler = logging.StreamHandler()
55
- stream_handler.setFormatter(formatter)
56
-
57
- logger.addHandler(file_handler)
58
- logger.addHandler(stream_handler)
59
-
60
  return logger
61
 
62
 
@@ -71,17 +69,23 @@ class ModelManager:
71
  self.model_loaded = False
72
 
73
  async def initialize(self) -> None:
 
 
 
 
74
  if not self.config.HF_TOKEN:
75
  logger.error("Token do Hugging Face não encontrado. O carregamento do modelo irá falhar.")
76
  return
77
 
78
  try:
79
  logger.info(f"A carregar o modelo: {self.config.MODEL_NAME}...")
 
80
  os.environ.setdefault("HF_TOKEN", self.config.HF_TOKEN)
81
 
82
  loop = asyncio.get_event_loop()
83
 
84
  def load_pipeline():
 
85
  return pipeline(
86
  "text-generation",
87
  model=self.config.MODEL_NAME,
@@ -98,11 +102,7 @@ class ModelManager:
98
 
99
  async def generate(self, request: GenerationRequest) -> Tuple[bool, str, int]:
100
  if not self.model_loaded or self.pipeline is None:
101
- return (
102
- False,
103
- "❌ O modelo não está disponível. Por favor, verifique os logs do servidor.",
104
- 0,
105
- )
106
 
107
  try:
108
  if not request.prompt.strip():
@@ -132,7 +132,7 @@ class ModelManager:
132
 
133
  generated_text = outputs[0].get("generated_text", "")
134
  if generated_text.startswith(prompt_text):
135
- generated_text = generated_text[len(prompt_text) :]
136
 
137
  tokens_used = 0
138
  if tokenizer and hasattr(tokenizer, "encode"):
@@ -161,11 +161,12 @@ class GemmaService:
161
  await self.model_manager.initialize()
162
 
163
  async def generate_text(self, api_key: str, prompt: str, **kwargs) -> APIResponse:
164
- if not api_key or not api_key.startswith("gsk-"):
 
165
  return APIResponse(success=False, error="Chave de API inválida ou ausente.")
166
  try:
167
- request = GenerationRequest(prompt=prompt, **kwargs)
168
- success, text, tokens_used = await self.model_manager.generate(request)
169
  if success:
170
  return APIResponse(success=True, data={"generated_text": text, "tokens_used": tokens_used})
171
  else:
@@ -175,22 +176,16 @@ class GemmaService:
175
  return APIResponse(success=False, error="Ocorreu um erro interno no serviço.")
176
 
177
 
178
- # ----------------- Enhanced UI -----------------
179
  class GradioInterface:
180
  def __init__(self, service: GemmaService):
181
  self.service = service
182
 
183
  def create_custom_css(self) -> str:
184
- # Importa Material Icons e adiciona ícones via pseudo-elementos nos botões
185
  return """
186
- /* importar Material Icons */
187
  @import url('https://fonts.googleapis.com/css2?family=Material+Icons&display=swap');
188
 
189
- :root {
190
- --dark-bg: #0a0a0a; --panel-bg: #1a1a1a; --border-color: #333;
191
- --text-color: #f0f0f0; --text-light: #a0a0a0; --accent-orange: #FF4500;
192
- --accent-orange-hover: #FF6347; --code-bg: #282c34;
193
- }
194
  .gradio-container { background: var(--dark-bg) !important; color: var(--text-color); }
195
  #main_layout { background: transparent; border: none !important; box-shadow: none !important; gap: 2rem; }
196
  #right_panel, #left_panel { background: var(--panel-bg); border: 1px solid var(--border-color); border-radius: 16px; padding: 2rem !important; }
@@ -201,99 +196,25 @@ class GradioInterface:
201
  #api_key_input textarea, #prompt_input textarea { background-color: #2C2C2C !important; border-color: var(--border-color) !important; color: var(--text-color) !important; border-radius: 12px !important; }
202
  #send_button { background: var(--accent-orange); color: white; border: none; border-radius: 12px !important; transition: background-color 0.3s ease; position: relative; padding-left: 3rem; }
203
  #send_button:hover { background-color: var(--accent-orange-hover); }
204
- #generate_button {
205
- background: linear-gradient(135deg, var(--accent-orange), var(--accent-orange-hover)); color: white !important;
206
- font-size: 1.1rem !important; font-weight: bold !important; border: none; border-radius: 12px !important;
207
- padding: 1rem 1.25rem !important; box-shadow: 0 4px 15px rgba(255, 69, 0, 0.4); transition: all 0.3s ease; position: relative; padding-left: 3rem;
208
- }
209
- #generate_button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 69, 0, 0.6); }
210
- h2, h3 { color: white; border-bottom: 1px solid var(--border-color); padding-bottom: 0.75rem; margin-bottom: 1.5rem; font-weight: 600; }
211
  .code-snippet { background-color: var(--code-bg); color: #abb2bf; padding: 1.5rem; border-radius: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word; border: 1px solid var(--border-color); }
212
- .code-snippet .keyword { color: #c678dd; } .code-snippet .string { color: #98c379; } .code-snippet .number { color: #d19a66; }
213
  .gr-slider { color: var(--text-light); }
214
 
215
- /* estilo para usar as Material Icons como ligatures */
216
- .material-icon {
217
- font-family: 'Material Icons', sans-serif;
218
- font-weight: normal;
219
- font-style: normal;
220
- font-size: 20px;
221
- line-height: 1;
222
- letter-spacing: normal;
223
- text-transform: none;
224
- display: inline-block;
225
- white-space: nowrap;
226
- word-wrap: normal;
227
- direction: ltr;
228
- -webkit-font-feature-settings: 'liga';
229
- -webkit-font-smoothing: antialiased;
230
- }
231
-
232
- /* adicionar ícones antes dos botões (usando ligatures) */
233
- #send_button::before {
234
- content: "send"; /* ligature do ícone */
235
- font-family: 'Material Icons', sans-serif;
236
- position: absolute;
237
- left: 12px;
238
- top: 50%;
239
- transform: translateY(-50%);
240
- font-size: 18px;
241
- line-height: 1;
242
- opacity: 0.95;
243
- }
244
-
245
- #generate_button::before {
246
- content: "auto_awesome";
247
- font-family: 'Material Icons', sans-serif;
248
- position: absolute;
249
- left: 12px;
250
- top: 50%;
251
- transform: translateY(-50%);
252
- font-size: 18px;
253
- line-height: 1;
254
- opacity: 0.95;
255
- }
256
-
257
- /* ícone para o botão de gerar chave (se usar outro botão, adapte o id) */
258
- #generate_button[aria-label], #generate_button[title] { /* fallback */
259
- padding-left: 3rem;
260
- }
261
-
262
- /* ícone ao lado do exemplo de código (vpn_key) */
263
- #right_panel .code-snippet::before {
264
- content: "vpn_key";
265
- font-family: 'Material Icons', sans-serif;
266
- display: inline-block;
267
- margin-right: 0.5rem;
268
- vertical-align: middle;
269
- font-size: 18px;
270
- opacity: 0.9;
271
- }
272
  """
273
 
274
  async def create_interface(self) -> gr.Blocks:
275
- with gr.Blocks(css=self.create_custom_css(), theme=None) as app:
276
  with gr.Row(elem_id="main_layout", equal_height=False):
277
  with gr.Column(scale=2):
278
  with gr.Column(elem_id="left_panel"):
279
- output_display = gr.Markdown(
280
- elem_id="output_display",
281
- value="<p style='color: #a0a0a0;'>A sua resposta aparecerá aqui...</p>",
282
- )
283
  with gr.Column(elem_id="input_area"):
284
- api_key_input = gr.Textbox(
285
- label="A Sua Chave de API",
286
- placeholder="Cole a sua chave gsk-... aqui",
287
- type="password",
288
- elem_id="api_key_input",
289
- )
290
  with gr.Row():
291
- prompt_input = gr.Textbox(
292
- show_label=False,
293
- placeholder="Digite a sua mensagem...",
294
- elem_id="prompt_input",
295
- scale=10,
296
- )
297
  send_button = gr.Button("➤ Enviar", elem_id="send_button", scale=2)
298
 
299
  with gr.Column(scale=1):
@@ -312,54 +233,42 @@ class GradioInterface:
312
 
313
  def handle_key_generation():
314
  key = f"gsk-{secrets.token_urlsafe(24).replace('_', '').replace('-', '')}"
315
- code_html = f"""
316
  <div class="code-snippet">
317
- <div><span class="keyword">import</span> requests</div>
318
- <div>&nbsp;</div>
319
- <div>url = <span class="string">"https://GuXSs.hf.space/run/generate"</span></div>
320
- <div>payload = {{</div>
321
- <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"api_key"</span>: <span class="string">"{key}"</span>,</div>
322
- <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"prompt"</span>: <span class="string">"Escreva um haikai sobre o universo"</span>,</div>
323
- <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"max_tokens"</span>: <span class="number">50</span></div>
324
- <div>}}</div>
325
- <div>&nbsp;</div>
326
- <div>response = requests.post(url, json=payload)</div>
327
- <div><span class="keyword">print</span>(response.json())</div>
328
  </div>
329
- """
330
  return key, gr.update(value=code_html)
331
 
332
  async def handle_generation(api_key, prompt, temp, max_tokens, top_k, top_p, btn):
 
333
  if not api_key:
334
- yield (
335
- "<p style='color: #FFCC00;'>Por favor, insira a sua chave de API para começar.</p>",
336
- gr.update(value="➤ Enviar", interactive=True),
337
- )
338
  return
339
  if not prompt:
340
- yield (
341
- "<p style='color: #FFCC00;'>Por favor, digite um prompt.</p>",
342
- gr.update(value="➤ Enviar", interactive=True),
343
- )
344
  return
345
 
346
  yield "<p style='color: #a0a0a0;'>A gerar resposta...</p>", gr.update(value="A gerar...", interactive=False)
347
 
348
- response = await self.service.generate_text(
349
- api_key=api_key,
350
- prompt=prompt,
351
- temperature=temp,
352
- max_tokens=int(max_tokens),
353
- top_k=int(top_k),
354
- top_p=top_p,
355
- )
356
-
357
  if response.success:
358
  formatted_text = html.escape(response.data["generated_text"]).replace("\n", "<br>")
359
  yield formatted_text, gr.update(value="➤ Enviar", interactive=True)
360
  else:
361
  yield f"<p style='color: #FF4500;'>{response.error}</p>", gr.update(value="➤ Enviar", interactive=True)
362
 
 
363
  send_button.click(
364
  handle_generation,
365
  inputs=[api_key_input, prompt_input, temp_slider, max_tokens_slider, top_k_slider, top_p_slider, send_button],
@@ -368,29 +277,95 @@ class GradioInterface:
368
  )
369
 
370
  key_button.click(handle_key_generation, outputs=[api_key_input, api_example_display])
 
371
 
372
- app.load(
373
- lambda: gr.update(value="<p style='color: #a0a0a0;'>Clique em 'Gerar Nova Chave' para ver um exemplo de código.</p>"),
374
- [],
375
- [api_example_display],
376
- )
377
 
378
- return app
379
 
 
 
 
380
 
381
- # ----------------- Main Application -----------------
382
- async def main():
383
  try:
384
- service = GemmaService()
385
- await service.initialize()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
386
 
387
- interface = GradioInterface(service)
388
- app = await interface.create_interface()
389
 
390
- app.launch(server_name="0.0.0.0", server_port=7860, share=False, debug=False)
391
- except Exception as e:
392
- logger.critical(f"Falha ao iniciar a aplicação: {e}", exc_info=True)
 
393
 
394
 
395
  if __name__ == "__main__":
396
- asyncio.run(main())
 
 
 
1
+ # app.py
2
  import os
3
  import secrets
4
  import logging
5
  import asyncio
6
  import html
 
7
  from dataclasses import dataclass
8
  from typing import Any, Optional, Tuple
9
 
 
11
  from transformers import pipeline
12
  from dotenv import load_dotenv
13
  from pydantic import BaseModel
14
+ from fastapi import FastAPI, Request
15
+ from fastapi.responses import JSONResponse
16
+ import uvicorn
17
 
18
  # ----------------- Configuration & Models -----------------
19
  load_dotenv()
 
41
  error: Optional[str] = None
42
 
43
 
44
+ # ----------------- Logger -----------------
45
  def setup_logger() -> logging.Logger:
46
  cfg = Config()
47
  log_level = getattr(logging, cfg.LOG_LEVEL.upper(), logging.INFO)
 
48
  logger = logging.getLogger("gemma_saas")
49
  if not logger.handlers:
50
  logger.setLevel(log_level)
51
  formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
52
+ fh = logging.FileHandler("gemma_saas.log")
53
+ fh.setFormatter(formatter)
54
+ sh = logging.StreamHandler()
55
+ sh.setFormatter(formatter)
56
+ logger.addHandler(fh)
57
+ logger.addHandler(sh)
 
 
 
 
58
  return logger
59
 
60
 
 
69
  self.model_loaded = False
70
 
71
  async def initialize(self) -> None:
72
+ """
73
+ Inicializa o pipeline. Usa HF_TOKEN (variável de ambiente ou Config).
74
+ Evita passar `use_auth_token` em model_kwargs.
75
+ """
76
  if not self.config.HF_TOKEN:
77
  logger.error("Token do Hugging Face não encontrado. O carregamento do modelo irá falhar.")
78
  return
79
 
80
  try:
81
  logger.info(f"A carregar o modelo: {self.config.MODEL_NAME}...")
82
+ # garante env var como fallback
83
  os.environ.setdefault("HF_TOKEN", self.config.HF_TOKEN)
84
 
85
  loop = asyncio.get_event_loop()
86
 
87
  def load_pipeline():
88
+ # Passa token diretamente (substitui use_auth_token)
89
  return pipeline(
90
  "text-generation",
91
  model=self.config.MODEL_NAME,
 
102
 
103
  async def generate(self, request: GenerationRequest) -> Tuple[bool, str, int]:
104
  if not self.model_loaded or self.pipeline is None:
105
+ return False, "❌ O modelo não está disponível. Por favor, verifique os logs do servidor.", 0
 
 
 
 
106
 
107
  try:
108
  if not request.prompt.strip():
 
132
 
133
  generated_text = outputs[0].get("generated_text", "")
134
  if generated_text.startswith(prompt_text):
135
+ generated_text = generated_text[len(prompt_text):]
136
 
137
  tokens_used = 0
138
  if tokenizer and hasattr(tokenizer, "encode"):
 
161
  await self.model_manager.initialize()
162
 
163
  async def generate_text(self, api_key: str, prompt: str, **kwargs) -> APIResponse:
164
+ # Validação simples da gsk-... chave da app
165
+ if not api_key or not isinstance(api_key, str) or not api_key.startswith("gsk-"):
166
  return APIResponse(success=False, error="Chave de API inválida ou ausente.")
167
  try:
168
+ req = GenerationRequest(prompt=prompt, **kwargs)
169
+ success, text, tokens_used = await self.model_manager.generate(req)
170
  if success:
171
  return APIResponse(success=True, data={"generated_text": text, "tokens_used": tokens_used})
172
  else:
 
176
  return APIResponse(success=False, error="Ocorreu um erro interno no serviço.")
177
 
178
 
179
+ # ----------------- Gradio UI -----------------
180
  class GradioInterface:
181
  def __init__(self, service: GemmaService):
182
  self.service = service
183
 
184
  def create_custom_css(self) -> str:
 
185
  return """
 
186
  @import url('https://fonts.googleapis.com/css2?family=Material+Icons&display=swap');
187
 
188
+ :root { --dark-bg:#0a0a0a; --panel-bg:#1a1a1a; --border-color:#333; --text-color:#f0f0f0; --text-light:#a0a0a0; --accent-orange:#FF4500; --accent-orange-hover:#FF6347; --code-bg:#282c34; }
 
 
 
 
189
  .gradio-container { background: var(--dark-bg) !important; color: var(--text-color); }
190
  #main_layout { background: transparent; border: none !important; box-shadow: none !important; gap: 2rem; }
191
  #right_panel, #left_panel { background: var(--panel-bg); border: 1px solid var(--border-color); border-radius: 16px; padding: 2rem !important; }
 
196
  #api_key_input textarea, #prompt_input textarea { background-color: #2C2C2C !important; border-color: var(--border-color) !important; color: var(--text-color) !important; border-radius: 12px !important; }
197
  #send_button { background: var(--accent-orange); color: white; border: none; border-radius: 12px !important; transition: background-color 0.3s ease; position: relative; padding-left: 3rem; }
198
  #send_button:hover { background-color: var(--accent-orange-hover); }
199
+ #generate_button { background: linear-gradient(135deg, var(--accent-orange), var(--accent-orange-hover)); color: white !important; font-size: 1.1rem !important; font-weight: bold !important; border: none; border-radius: 12px !important; padding: 1rem 1.25rem !important; box-shadow: 0 4px 15px rgba(255,69,0,0.4); transition: all 0.3s ease; position: relative; padding-left: 3rem; }
200
+ #generate_button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255,69,0,0.6); }
 
 
 
 
 
201
  .code-snippet { background-color: var(--code-bg); color: #abb2bf; padding: 1.5rem; border-radius: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word; border: 1px solid var(--border-color); }
 
202
  .gr-slider { color: var(--text-light); }
203
 
204
+ #send_button::before { content: "send"; font-family: 'Material Icons', sans-serif; position:absolute; left:12px; top:50%; transform:translateY(-50%); font-size:18px; opacity:0.95; }
205
+ #generate_button::before { content: "auto_awesome"; font-family: 'Material Icons', sans-serif; position:absolute; left:12px; top:50%; transform:translateY(-50%); font-size:18px; opacity:0.95; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  """
207
 
208
  async def create_interface(self) -> gr.Blocks:
209
+ with gr.Blocks(css=self.create_custom_css(), theme=None) as demo:
210
  with gr.Row(elem_id="main_layout", equal_height=False):
211
  with gr.Column(scale=2):
212
  with gr.Column(elem_id="left_panel"):
213
+ output_display = gr.Markdown(elem_id="output_display", value="<p style='color: #a0a0a0;'>A sua resposta aparecerá aqui...</p>")
 
 
 
214
  with gr.Column(elem_id="input_area"):
215
+ api_key_input = gr.Textbox(label="A Sua Chave de API", placeholder="Cole a sua chave gsk-... aqui", type="password", elem_id="api_key_input")
 
 
 
 
 
216
  with gr.Row():
217
+ prompt_input = gr.Textbox(show_label=False, placeholder="Digite a sua mensagem...", elem_id="prompt_input", scale=10)
 
 
 
 
 
218
  send_button = gr.Button("➤ Enviar", elem_id="send_button", scale=2)
219
 
220
  with gr.Column(scale=1):
 
233
 
234
  def handle_key_generation():
235
  key = f"gsk-{secrets.token_urlsafe(24).replace('_', '').replace('-', '')}"
236
+ code_html = f'''
237
  <div class="code-snippet">
238
+ <div><span class="keyword">import</span> requests</div>
239
+ <div>&nbsp;</div>
240
+ <div>url = <span class="string">"https://GuXSs.hf.space/api/generate"</span></div>
241
+ <div>payload = {{</div>
242
+ <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"api_key"</span>: <span class="string">"{key}"</span>,</div>
243
+ <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"prompt"</span>: <span class="string">"Escreva um haikai sobre o universo"</span>,</div>
244
+ <div>&nbsp;&nbsp;&nbsp;&nbsp;<span class="string">"max_tokens"</span>: <span class="number">50</span></div>
245
+ <div>}}</div>
246
+ <div>&nbsp;</div>
247
+ <div>response = requests.post(url, json=payload)</div>
248
+ <div><span class="keyword">print</span>(response.json())</div>
249
  </div>
250
+ '''
251
  return key, gr.update(value=code_html)
252
 
253
  async def handle_generation(api_key, prompt, temp, max_tokens, top_k, top_p, btn):
254
+ # função que Gradio chama quando botão é pressionado na UI
255
  if not api_key:
256
+ yield "<p style='color: #FFCC00;'>Por favor, insira a sua chave de API para começar.</p>", gr.update(value="➤ Enviar", interactive=True)
 
 
 
257
  return
258
  if not prompt:
259
+ yield "<p style='color: #FFCC00;'>Por favor, digite um prompt.</p>", gr.update(value="➤ Enviar", interactive=True)
 
 
 
260
  return
261
 
262
  yield "<p style='color: #a0a0a0;'>A gerar resposta...</p>", gr.update(value="A gerar...", interactive=False)
263
 
264
+ response = await self.service.generate_text(api_key=api_key, prompt=prompt, temperature=temp, max_tokens=int(max_tokens), top_k=int(top_k), top_p=top_p)
 
 
 
 
 
 
 
 
265
  if response.success:
266
  formatted_text = html.escape(response.data["generated_text"]).replace("\n", "<br>")
267
  yield formatted_text, gr.update(value="➤ Enviar", interactive=True)
268
  else:
269
  yield f"<p style='color: #FF4500;'>{response.error}</p>", gr.update(value="➤ Enviar", interactive=True)
270
 
271
+ # conectar o callback da UI — nome API usado por Gradio será "generate"
272
  send_button.click(
273
  handle_generation,
274
  inputs=[api_key_input, prompt_input, temp_slider, max_tokens_slider, top_k_slider, top_p_slider, send_button],
 
277
  )
278
 
279
  key_button.click(handle_key_generation, outputs=[api_key_input, api_example_display])
280
+ demo.load(lambda: gr.update(value="<p style='color: #a0a0a0;'>Clique em 'Gerar Nova Chave' para ver um exemplo de código.</p>"), [], [api_example_display])
281
 
282
+ return demo
 
 
 
 
283
 
 
284
 
285
+ # ----------------- FastAPI + endpoints -----------------
286
+ def create_fastapi_app(gradio_blocks: gr.Blocks, service: GemmaService) -> FastAPI:
287
+ fast_app = FastAPI(title="Gemma Service (Gradio + API)")
288
 
289
+ # monta a UI Gradio na raiz "/" (usa mount_gradio_app)
 
290
  try:
291
+ # função disponibilizada por versões recentes do gradio
292
+ gr.mount_gradio_app(fast_app, gradio_blocks, path="/")
293
+ except Exception as exc:
294
+ logger.warning("Não foi possível montar Gradio com mount_gradio_app: %s. A UI pode não funcionar embutida.", exc)
295
+
296
+ @fast_app.post("/api/generate")
297
+ async def api_generate(req: Request):
298
+ """
299
+ Endpoint REST "amigável" que aceita JSON:
300
+ { "api_key": "...", "prompt": "...", "max_tokens": 128, "temperature": 0.7, "top_k": 50, "top_p": 0.95 }
301
+ """
302
+ try:
303
+ body = await req.json()
304
+ except Exception:
305
+ return JSONResponse(status_code=400, content={"success": False, "error": "Payload inválido (JSON esperado)."})
306
+
307
+ api_key = body.get("api_key")
308
+ prompt = body.get("prompt", "")
309
+ max_tokens = int(body.get("max_tokens", 512))
310
+ temperature = float(body.get("temperature", 0.7))
311
+ top_k = int(body.get("top_k", 50))
312
+ top_p = float(body.get("top_p", 0.95))
313
+
314
+ resp = await service.generate_text(api_key=api_key, prompt=prompt, max_tokens=max_tokens, temperature=temperature, top_k=top_k, top_p=top_p)
315
+ status = 200 if resp.success else 400
316
+ return JSONResponse(status_code=status, content=resp.dict())
317
+
318
+ @fast_app.post("/run/generate")
319
+ async def gradio_compatible_generate(req: Request):
320
+ """
321
+ Endpoint compatível com o formato 'Gradio' (data array).
322
+ Exemplo:
323
+ { "data": [ "gsk-..", "prompt...", 128, 0.7, 50, 0.95 ] }
324
+ """
325
+ try:
326
+ body = await req.json()
327
+ except Exception:
328
+ return JSONResponse(status_code=400, content={"success": False, "error": "Payload inválido (JSON esperado)."})
329
+
330
+ data = body.get("data")
331
+ if not isinstance(data, list):
332
+ return JSONResponse(status_code=400, content={"success": False, "error": "Campo 'data' inválido. Esperado array."})
333
+
334
+ # mapear por posições (compatível com a UI)
335
+ try:
336
+ api_key = data[0]
337
+ prompt = data[1] if len(data) > 1 else ""
338
+ max_tokens = int(data[2]) if len(data) > 2 else 512
339
+ temperature = float(data[3]) if len(data) > 3 else 0.7
340
+ top_k = int(data[4]) if len(data) > 4 else 50
341
+ top_p = float(data[5]) if len(data) > 5 else 0.95
342
+ except Exception as e:
343
+ return JSONResponse(status_code=400, content={"success": False, "error": f"Erro ao parsear 'data': {e}"})
344
+
345
+ resp = await service.generate_text(api_key=api_key, prompt=prompt, max_tokens=max_tokens, temperature=temperature, top_k=top_k, top_p=top_p)
346
+ status = 200 if resp.success else 400
347
+ return JSONResponse(status_code=status, content=resp.dict())
348
+
349
+ return fast_app
350
+
351
+
352
+ # ----------------- Entrypoint -----------------
353
+ async def build_and_run():
354
+ service = GemmaService()
355
+ await service.initialize()
356
+
357
+ interface = GradioInterface(service)
358
+ gradio_blocks = await interface.create_interface()
359
 
360
+ fast_app = create_fastapi_app(gradio_blocks, service)
 
361
 
362
+ # Quando executado localmente com "python app.py", usamos uvicorn para servir.
363
+ # No Hugging Face Spaces, o arquivo app.py será automaticamente usado (uvicorn não é necessário manualmente),
364
+ # mas manter este bloco para execução local.
365
+ return fast_app
366
 
367
 
368
  if __name__ == "__main__":
369
+ # Constrói app (inicializa modelo) e executa uvicorn
370
+ fast_app = asyncio.run(build_and_run())
371
+ uvicorn.run(fast_app, host="0.0.0.0", port=int(os.getenv("PORT", 7860)))