@danhachuel/thunderbolt 0.5.60 → 0.5.61
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
|
@@ -7281,6 +7281,9 @@ def _persist_media_cards(settings: dict[str, Any], cards: list[dict[str, Any]],
|
|
|
7281
7281
|
def _fetch_media_models(card: dict[str, Any]) -> list[str]:
|
|
7282
7282
|
"""Consultar modelos do provider, incluindo o catálogo nativo Gemini."""
|
|
7283
7283
|
provider = str(card.get("provider") or "").strip().lower()
|
|
7284
|
+
if provider == "huggingface":
|
|
7285
|
+
from integrations.openai_model_discovery import fetch_huggingface_text_to_image_models
|
|
7286
|
+
return fetch_huggingface_text_to_image_models(str(card.get("api_key") or ""))
|
|
7284
7287
|
if provider == "replicate":
|
|
7285
7288
|
from integrations.openai_model_discovery import fetch_replicate_models
|
|
7286
7289
|
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