@danhachuel/pandanews 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Todas as versões publicadas são registradas aqui antes do publish (regra em AGENTS.md). A etiqueta visual usa PATCH de dois dígitos (ex.: 0.1.00); o registry npm normaliza para a versão canônica (ex.: 0.1.0).
4
4
 
5
+ ## 0.4.03 (2026-11-08)
6
+
7
+ - **Regra permanente no AGENTS.md**: é PROIBIDO criar/publicar versões que abram janelas de terminal (CMD/PowerShell/console Python) no computador do usuário — todo processo filho deve nascer com `windowsHide: true` + grupo isolado, rodar em background com saída apenas em log e ser encerrado por código; versão que exiba janela de console é inválida (hotfix + CHANGELOG)
8
+ - Teste de regressão reforçado: varre **todas** as chamadas `spawn`/`spawnSync` do launcher e falha se qualquer uma perder `windowsHide`/`detached` — o CI passa a barrar isso automaticamente
9
+ - `taskkill` do encerramento gracioso também com `detached: true` (uniformidade da regra)
10
+
11
+ ## 0.4.02 (2026-11-08)
12
+
13
+ - **Seletor dinâmico de modelos NVIDIA NIM**: novo endpoint proxy `GET /api/v1/configuracoes/llm/nvidia/models` que consulta a NVIDIA em tempo real com a chave do usuário — a API key nunca é exposta ao frontend/Network tab
14
+ - Campo "Modelo" da NVIDIA em Configurações agora é um `<select>` populado automaticamente (5 min de cache, botão ↻ para recarregar) — o usuário não digita mais IDs
15
+ - Resiliência: sem chave, offline ou lista indisponível, o select mantém o modelo atual como opção (fallback) com aviso âmbar — nada de travar a tela
16
+
5
17
  ## 0.4.01 (2026-11-08)
6
18
 
7
19
  - **Isolamento de processos (Windows/MobaXterm)**: o backend Python roda em grupo de processos próprio (`detached: true` → `CREATE_NEW_PROCESS_GROUP`) com `windowsHide: true` (sem janela fantasma) e `stdio` em fd do arquivo — um Ctrl+C no terminal agora derruba **apenas o launcher Node**; o backend e as gerações em andamento sobrevivem
@@ -31,7 +31,7 @@ async def lifespan(_: FastAPI):
31
31
  scheduler.parar()
32
32
 
33
33
 
34
- app = FastAPI(title="PandaNews API", version="0.4.1", lifespan=lifespan)
34
+ app = FastAPI(title="PandaNews API", version="0.4.3", lifespan=lifespan)
35
35
 
36
36
  app.add_middleware(
37
37
  CORSMiddleware,
@@ -1,7 +1,7 @@
1
1
  """Configurações (/api/v1/configuracoes) — API keys mascaradas, sem travas."""
2
2
  from __future__ import annotations
3
3
 
4
- from fastapi import APIRouter, Depends
4
+ from fastapi import APIRouter, Depends, HTTPException
5
5
  from sqlalchemy.orm import Session
6
6
 
7
7
  from backend.app.schemas import ConfiguracoesPatch
@@ -43,6 +43,34 @@ def obter_configuracoes(db: Session = Depends(get_db)) -> dict:
43
43
  }
44
44
 
45
45
 
46
+ @router.get("/llm/nvidia/models")
47
+ def listar_modelos_nvidia(db: Session = Depends(get_db)) -> dict:
48
+ """Lista os modelos NVIDIA NIM disponíveis para a chave configurada.
49
+
50
+ Proxy: a API key fica no banco local e nunca é exposta ao frontend.
51
+ Retorna 400 sem chave; 502 em falha de consulta.
52
+ """
53
+ import httpx
54
+
55
+ from backend.core.llm_router import provider_config
56
+
57
+ cfg = provider_config(db, "nvidia")
58
+ if not cfg["api_key"]:
59
+ raise HTTPException(status_code=400, detail="API Key da NVIDIA não configurada — adicione-a antes de listar os modelos.")
60
+ url = cfg["base_url"].rstrip("/") + "/models"
61
+ try:
62
+ resposta = httpx.get(url, headers={"Authorization": f"Bearer {cfg['api_key']}"}, timeout=20.0)
63
+ except httpx.HTTPError as exc:
64
+ raise HTTPException(status_code=502, detail=f"Erro ao consultar a NVIDIA: {exc}") from exc
65
+ if resposta.status_code != 200:
66
+ raise HTTPException(
67
+ status_code=502,
68
+ detail=f"NVIDIA respondeu {resposta.status_code}: {resposta.text[:200]}",
69
+ )
70
+ ids = sorted(str(m.get("id")) for m in resposta.json().get("data", []) if m.get("id"))
71
+ return {"models": ids, "modelo_atual": cfg["model"], "base_url": cfg["base_url"]}
72
+
73
+
46
74
  @router.post("/testar-supabase")
47
75
  def testar_supabase() -> dict:
48
76
  from backend.core import supabase_db