@danhachuel/thunderbolt 0.5.60 → 0.5.62
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/app/main.py
CHANGED
|
@@ -1332,6 +1332,9 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
|
|
|
1332
1332
|
edited_style = st.selectbox("Fonte do vídeo", WIDE_STYLE_OPTIONS, index=WIDE_STYLE_OPTIONS.index(channel_video_source_value(channel.get("style_wide"))) if channel_video_source_value(channel.get("style_wide")) in WIDE_STYLE_OPTIONS else 0)
|
|
1333
1333
|
edited_aspect = st.selectbox("Proporção do vídeo", CHANNEL_ASPECT_RATIO_OPTIONS, index=CHANNEL_ASPECT_RATIO_OPTIONS.index(str(channel.get("video_aspect_ratio") or "Landscape 16:9")) if str(channel.get("video_aspect_ratio") or "Landscape 16:9") in CHANNEL_ASPECT_RATIO_OPTIONS else 0)
|
|
1334
1334
|
edited_niche = st.text_input("Nicho", value=str(channel.get("niche") or channel_niche_label(channel) if channel_niche_label(channel) != "SEM NICHO CONFIGURADO" else "") )
|
|
1335
|
+
edited_subscribers = st.number_input("Inscritos", min_value=0, value=int(str(channel.get("subscriber_count") or 0).replace(".", "").replace(",", "")), step=1)
|
|
1336
|
+
edited_videos = st.number_input("Vídeos", min_value=0, value=int(str(channel.get("video_count") or 0).replace(".", "").replace(",", "")), step=1)
|
|
1337
|
+
edited_views = st.number_input("Visualizações", min_value=0, value=int(str(channel.get("view_count") or 0).replace(".", "").replace(",", "")), step=1)
|
|
1335
1338
|
with edit_cols[1]:
|
|
1336
1339
|
edited_blueprint = st.selectbox("Blueprint Padrão", blueprint_ids, index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"))
|
|
1337
1340
|
edited_voice = st.selectbox("Narrador/Voz Padrão", voice_options, index=voice_options.index(current_voice) if current_voice in voice_options else 0, format_func=lambda item: item or "Sem voz padrão")
|
|
@@ -1358,6 +1361,7 @@ def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], yout
|
|
|
1358
1361
|
"default_voice": edited_voice.strip(), "voice": edited_voice.strip(),
|
|
1359
1362
|
"google_account_id": edited_account.strip(), "google_account_email": str(youtube_accounts_by_id.get(edited_account, {}).get("email", "")),
|
|
1360
1363
|
"description": edited_description.strip(), "automation_on": bool(edited_automation), "automation_time": edited_time.strip(),
|
|
1364
|
+
"subscriber_count": int(edited_subscribers) or None, "video_count": int(edited_videos) or None, "view_count": int(edited_views) or None,
|
|
1361
1365
|
})
|
|
1362
1366
|
st.session_state.pop(f"edit_channel_{channel_id}", None)
|
|
1363
1367
|
st.success("Canal actualizado.")
|
|
@@ -7281,6 +7285,9 @@ def _persist_media_cards(settings: dict[str, Any], cards: list[dict[str, Any]],
|
|
|
7281
7285
|
def _fetch_media_models(card: dict[str, Any]) -> list[str]:
|
|
7282
7286
|
"""Consultar modelos do provider, incluindo o catálogo nativo Gemini."""
|
|
7283
7287
|
provider = str(card.get("provider") or "").strip().lower()
|
|
7288
|
+
if provider == "huggingface":
|
|
7289
|
+
from integrations.openai_model_discovery import fetch_huggingface_text_to_image_models
|
|
7290
|
+
return fetch_huggingface_text_to_image_models(str(card.get("api_key") or ""))
|
|
7284
7291
|
if provider == "replicate":
|
|
7285
7292
|
from integrations.openai_model_discovery import fetch_replicate_models
|
|
7286
7293
|
return fetch_replicate_models(str(card.get("api_key") or ""), str(card.get("base_url") or ""))
|
|
@@ -169,6 +169,14 @@ def test_media_provider_card(card: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
169
169
|
api_style = str(source.get("api_style") or "").strip().lower()
|
|
170
170
|
if provider == "nano_banana":
|
|
171
171
|
return test_nano_banana_credentials(api_key, model)
|
|
172
|
+
if provider == "huggingface":
|
|
173
|
+
if not api_key:
|
|
174
|
+
return _missing("Introduza o token Hugging Face antes de testar.")
|
|
175
|
+
return _get(
|
|
176
|
+
"https://huggingface.co/api/models",
|
|
177
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
178
|
+
params={"pipeline_tag": "text-to-image", "limit": 1},
|
|
179
|
+
)
|
|
172
180
|
if not base_url:
|
|
173
181
|
return _result("missing", "Complete a Base URL antes de testar.")
|
|
174
182
|
if provider not in {"inferenceport", "ollama", "lmstudio"} and not api_key:
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
11
|
import binascii
|
|
12
|
+
import io
|
|
12
13
|
import json
|
|
13
14
|
import mimetypes
|
|
14
15
|
import os
|
|
@@ -453,6 +454,35 @@ def generate_image_for_card(
|
|
|
453
454
|
ensure_storage()
|
|
454
455
|
destination = STORAGE / "thumbnails" / f"media-{provider}-{abs(hash((topic, prompt, variant_index))) & 0xffffffffffffffff:x}.jpg"
|
|
455
456
|
|
|
457
|
+
if provider == "huggingface":
|
|
458
|
+
model = _model(card)
|
|
459
|
+
token = _api_key(card)
|
|
460
|
+
if not model:
|
|
461
|
+
raise MediaGenerationError("Hugging Face requer a selecção de um modelo text-to-image.")
|
|
462
|
+
try:
|
|
463
|
+
image_prompt, _headline = _compose_thumbnail_prompt(
|
|
464
|
+
prompt,
|
|
465
|
+
topic=topic,
|
|
466
|
+
lettering_text=lettering_text,
|
|
467
|
+
lettering_prompt=lettering_prompt,
|
|
468
|
+
)
|
|
469
|
+
constrained_prompt = _append_generation_constraints(
|
|
470
|
+
image_prompt,
|
|
471
|
+
kind="image",
|
|
472
|
+
aspect_ratio="16:9",
|
|
473
|
+
size="1280x720 minimum",
|
|
474
|
+
)
|
|
475
|
+
from huggingface_hub import InferenceClient
|
|
476
|
+
image = InferenceClient(token=token or None, provider="hf-inference").text_to_image(
|
|
477
|
+
prompt=constrained_prompt,
|
|
478
|
+
model=model,
|
|
479
|
+
)
|
|
480
|
+
buffer = io.BytesIO()
|
|
481
|
+
image.save(buffer, format="PNG")
|
|
482
|
+
return _download_or_write(buffer.getvalue(), "", destination, card)
|
|
483
|
+
except Exception as exc:
|
|
484
|
+
raise MediaGenerationError(f"Hugging Face text-to-image falhou: {str(exc)[:240]}") from exc
|
|
485
|
+
|
|
456
486
|
def request(current: dict[str, Any]) -> Any:
|
|
457
487
|
return _image_request(current, prompt, topic=topic, lettering_text=lettering_text, lettering_prompt=lettering_prompt)
|
|
458
488
|
|
|
@@ -113,11 +113,11 @@ MEDIA_PROVIDER_CATALOG: tuple[MediaProviderDefinition, ...] = (
|
|
|
113
113
|
MediaProviderDefinition(
|
|
114
114
|
"huggingface",
|
|
115
115
|
"Hugging Face Inference API",
|
|
116
|
-
default_base_url="https://
|
|
116
|
+
default_base_url="https://huggingface.co",
|
|
117
117
|
supports_image=True,
|
|
118
|
-
supports_text=
|
|
118
|
+
supports_text=False,
|
|
119
119
|
api_style="huggingface",
|
|
120
|
-
description="Inference
|
|
120
|
+
description="Hugging Face Inference API para imagens via InferenceClient com provider hf-inference; não usa endpoint OpenAI-compatible.",
|
|
121
121
|
),
|
|
122
122
|
MediaProviderDefinition(
|
|
123
123
|
"cloudflare_workers_ai",
|
|
@@ -230,7 +230,7 @@ def normalize_media_card(card: Any, index: int = 0) -> dict[str, Any]:
|
|
|
230
230
|
"provider": provider,
|
|
231
231
|
"api_key": str(source.get("api_key") or source.get("key") or "").strip(),
|
|
232
232
|
"model": str(source.get("model") or source.get("model_name") or "").strip(),
|
|
233
|
-
"base_url": str(source.get("base_url") or "").strip() or definition.default_base_url,
|
|
233
|
+
"base_url": definition.default_base_url if provider == "huggingface" else (str(source.get("base_url") or "").strip() or definition.default_base_url),
|
|
234
234
|
"enabled": bool(source.get("enabled", True)),
|
|
235
235
|
"priority": max(0, int(source.get("priority", index)) if str(source.get("priority", index)).strip().lstrip("-").isdigit() else index),
|
|
236
236
|
"supports_image": bool(source.get("supports_image", definition.supports_image)),
|
|
@@ -210,6 +210,40 @@ def fetch_openai_compatible_models(
|
|
|
210
210
|
return normalize_model_ids(payload)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
def fetch_huggingface_text_to_image_models(
|
|
214
|
+
api_key: str,
|
|
215
|
+
*,
|
|
216
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
217
|
+
limit: int = 2000,
|
|
218
|
+
) -> list[str]:
|
|
219
|
+
"""List only public Hugging Face models tagged with ``text-to-image``."""
|
|
220
|
+
token = str(api_key or "").strip()
|
|
221
|
+
headers = {"Accept": "application/json"}
|
|
222
|
+
if token:
|
|
223
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
224
|
+
try:
|
|
225
|
+
response = requests.get(
|
|
226
|
+
"https://huggingface.co/api/models",
|
|
227
|
+
params={"pipeline_tag": "text-to-image", "limit": min(max(int(limit), 1), MAX_MODEL_IDS), "sort": "downloads", "direction": -1},
|
|
228
|
+
headers=headers,
|
|
229
|
+
timeout=timeout,
|
|
230
|
+
)
|
|
231
|
+
except requests.RequestException as exc:
|
|
232
|
+
raise ModelDiscoveryError(f"Não foi possível consultar o catálogo text-to-image da Hugging Face: {exc}") from exc
|
|
233
|
+
if response.status_code >= 400:
|
|
234
|
+
raise ModelDiscoveryError(f"O catálogo text-to-image da Hugging Face devolveu HTTP {response.status_code}.")
|
|
235
|
+
try:
|
|
236
|
+
payload = response.json()
|
|
237
|
+
except ValueError as exc:
|
|
238
|
+
raise ModelDiscoveryError("O catálogo Hugging Face devolveu uma resposta que não é JSON.") from exc
|
|
239
|
+
if not isinstance(payload, list):
|
|
240
|
+
raise ModelDiscoveryError("O catálogo Hugging Face não devolveu uma lista de modelos.")
|
|
241
|
+
models = sorted({str(item.get("id") or "").strip() for item in payload if isinstance(item, dict) and str(item.get("id") or "").strip()}, key=str.casefold)
|
|
242
|
+
if not models:
|
|
243
|
+
raise ModelDiscoveryError("A Hugging Face não devolveu modelos com pipeline text-to-image.")
|
|
244
|
+
return models[:MAX_MODEL_IDS]
|
|
245
|
+
|
|
246
|
+
|
|
213
247
|
def fetch_replicate_models(
|
|
214
248
|
api_key: str,
|
|
215
249
|
base_url: str,
|
package/package.json
CHANGED