@danhachuel/thunderbolt 0.5.51 → 0.5.53
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.
|
@@ -39,7 +39,7 @@ def _safe_int(value: Any, default: int = 0) -> int:
|
|
|
39
39
|
return default
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _public_videos(channel_url: str, limit: int =
|
|
42
|
+
def _public_videos(channel_url: str, limit: int = 3) -> list[dict[str, Any]]:
|
|
43
43
|
try:
|
|
44
44
|
import yt_dlp
|
|
45
45
|
except ImportError as exc:
|
|
@@ -187,7 +187,7 @@ def _report_markdown(record: Mapping[str, Any]) -> str:
|
|
|
187
187
|
pillar_names = {"Demanda validada": "Demanda", "Qualidade da thumbnail": "Thumbnail", "Qualidade do título": "Título", "Hook — retenção inicial": "Hook", "Ritmo e edição": "Pacing", "Origem do tráfego": "Tráfego", "Conversão em inscritos": "CTA", "Cadência de publicação": "Cadência"}
|
|
188
188
|
for metric in record.get("metrics", []):
|
|
189
189
|
lines.append(f"| **{pillar_names.get(metric['label'], metric['label'])}** | {metric['score']}/100 | {metric['value']} | {metric['diagnosis']} |")
|
|
190
|
-
lines += ["", "## 3. Últimos
|
|
190
|
+
lines += ["", "## 3. Últimos 3 vídeos", "", "| Título | Visualizações | Thumbnail | Transcrição | Nota do título |", "|---|---:|---|---|---:|"]
|
|
191
191
|
for video in record.get("videos", []):
|
|
192
192
|
lines.append(f"| {video.get('title', '').replace('|', '/')} | {video.get('view_count', 0):,} | {video.get('thumbnail_status', '')} | {video.get('transcript_status', '')} | {video.get('title_score', 0)}/100 |")
|
|
193
193
|
lines += ["", "## 4. Actionable Roadmap — Top 3 prioridades", "", "1. **REMAKE THUMBNAILS** dos vídeos abaixo de 70: aumentar contraste, sujeito claro, emoção forte e máximo de três palavras legíveis no telemóvel.", "2. **REWRITE TITLES** com Curiosity, Clarity e Urgency; colocar a keyword principal no início e usar números/adjectivos de poder quando forem verdadeiros.", "3. **EDIT HOOK**: começar pelo resultado, promessa ou estatística forte e introduzir pattern interrupts a cada 5–8 segundos.", "", "## 5. Estratégia de longo prazo", "", "Construir uma biblioteca de pelo menos 50 vídeos evergreen, publicar com cadência semanal fixa e procurar que Browse Features se torne a principal origem de tráfego. Para um criador solo, consistência supera frequência; melhorar Thumbnail + Título antes de alterar a produção.", "", "## Limitações", "", "CTR, retenção real, origem de tráfego, RPM e horas de visualização exigem dados autorizados do YouTube Studio e não são inventados a partir da página pública."]
|
|
@@ -202,9 +202,9 @@ def run_audit(channel: Mapping[str, Any], settings: Mapping[str, Any], *, vision
|
|
|
202
202
|
channel_url = str(channel.get("url") or channel.get("handle") or "").strip()
|
|
203
203
|
if not channel_url:
|
|
204
204
|
raise RuntimeError("O canal seleccionado não tem URL pública ou handle YouTube.")
|
|
205
|
-
notify("A buscar o canal e os
|
|
205
|
+
notify("A buscar o canal e os 3 vídeos mais recentes…")
|
|
206
206
|
try:
|
|
207
|
-
videos = _public_videos(channel_url,
|
|
207
|
+
videos = _public_videos(channel_url, 3)
|
|
208
208
|
except Exception as exc:
|
|
209
209
|
failed = {"code": code, "channel_id": str(channel.get("id") or ""), "channel_name": str(channel.get("name") or "Canal"), "platform": "YouTube", "created_at": datetime.now(timezone.utc).isoformat(), "status": "failed", "error": str(exc)[:500]}
|
|
210
210
|
analyses = read_json(ANALYSES_FILENAME, [])
|
|
@@ -266,37 +266,23 @@ def validate_paligemma_api_key(
|
|
|
266
266
|
*,
|
|
267
267
|
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
268
268
|
) -> None:
|
|
269
|
-
"""Validate the
|
|
269
|
+
"""Validate the NIM credential through its authenticated model catalog."""
|
|
270
270
|
token = str(api_key or "").strip()
|
|
271
271
|
if not token:
|
|
272
272
|
raise OpenAICompatibleAPIError("Informe a API key antes do teste.")
|
|
273
273
|
configured = str(base_url or DEFAULT_NVIDIA_NIM_BASE_URL).rstrip("/")
|
|
274
274
|
if configured.endswith("/chat/completions"):
|
|
275
275
|
configured = configured.removesuffix("/chat/completions")
|
|
276
|
-
if
|
|
277
|
-
configured = configured.replace("integrate.api.nvidia.com", "ai.api.nvidia.com")
|
|
278
|
-
endpoint = configured if "/vlm/google/paligemma" in configured else f"{configured}/vlm/google/paligemma"
|
|
279
|
-
# O endpoint VLM devolve 422 quando recebe apenas texto; uma imagem mínima
|
|
280
|
-
# permite testar autenticação e validação do contrato sem usar ficheiros do utilizador.
|
|
281
|
-
one_pixel_png = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
|
|
282
|
-
payload = {
|
|
283
|
-
"messages": [{"role": "user", "content": [
|
|
284
|
-
{"type": "text", "text": "Describe this image in one word."},
|
|
285
|
-
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{one_pixel_png}"}},
|
|
286
|
-
]}],
|
|
287
|
-
"max_tokens": 8,
|
|
288
|
-
"temperature": 0,
|
|
289
|
-
}
|
|
276
|
+
endpoint = configured if configured.endswith("/models") else f"{configured}/models"
|
|
290
277
|
try:
|
|
291
|
-
response = requests.
|
|
278
|
+
response = requests.get(
|
|
292
279
|
endpoint,
|
|
293
|
-
headers={"Accept": "application/json", "Authorization": f"Bearer {token}"
|
|
294
|
-
json=payload,
|
|
280
|
+
headers={"Accept": "application/json", "Authorization": f"Bearer {token}"},
|
|
295
281
|
timeout=timeout,
|
|
296
282
|
)
|
|
297
283
|
except requests.RequestException as exc:
|
|
298
|
-
raise OpenAICompatibleAPIError(f"Não foi possível testar o
|
|
284
|
+
raise OpenAICompatibleAPIError(f"Não foi possível testar o catálogo NVIDIA NIM: {exc}") from exc
|
|
299
285
|
if response.status_code >= 400:
|
|
300
286
|
if response.status_code in (401, 403):
|
|
301
|
-
raise OpenAICompatibleAPIError(f"O
|
|
302
|
-
raise OpenAICompatibleAPIError(f"O
|
|
287
|
+
raise OpenAICompatibleAPIError(f"O catálogo NVIDIA NIM recusou a API key (HTTP {response.status_code}).")
|
|
288
|
+
raise OpenAICompatibleAPIError(f"O catálogo NVIDIA NIM devolveu HTTP {response.status_code}.")
|
package/package.json
CHANGED