@danhachuel/thunderbolt 0.5.34 → 0.5.36
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
|
@@ -7103,6 +7103,9 @@ def _persist_media_cards(settings: dict[str, Any], cards: list[dict[str, Any]],
|
|
|
7103
7103
|
def _fetch_media_models(card: dict[str, Any]) -> list[str]:
|
|
7104
7104
|
"""Consultar modelos do provider, incluindo o catálogo nativo Gemini."""
|
|
7105
7105
|
provider = str(card.get("provider") or "").strip().lower()
|
|
7106
|
+
if provider == "replicate":
|
|
7107
|
+
from integrations.openai_model_discovery import fetch_replicate_models
|
|
7108
|
+
return fetch_replicate_models(str(card.get("api_key") or ""), str(card.get("base_url") or ""))
|
|
7106
7109
|
if provider == "kie_ai":
|
|
7107
7110
|
return list(KIE_MEDIA_MODEL_CATALOG)
|
|
7108
7111
|
if provider == "fal_ai":
|
|
@@ -177,6 +177,20 @@ def _first_page(value: Any) -> tuple[str, list[dict[str, Any]]]:
|
|
|
177
177
|
return page_id, pages
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def _thumbnail_layout_instructions() -> str:
|
|
181
|
+
"""Return hard layout constraints for Canva's generated editable designs."""
|
|
182
|
+
return (
|
|
183
|
+
"LAYOUT REQUIREMENTS (mandatory): use separate editable bounding boxes for every text item; "
|
|
184
|
+
"never stack or merge text, numbers, badges, or icons. Keep a safe margin of at least 5% on all sides. "
|
|
185
|
+
"Use a clear 16:9 grid: primary headline in the left 58% of the canvas, secondary text below it with "
|
|
186
|
+
"at least 4% vertical separation, and any number/stat badge in the upper-right 30% inside its own box. "
|
|
187
|
+
"Place icons or decorative symbols in the right-side visual area, separated from every text box by at least 3% "
|
|
188
|
+
"of canvas width. Each box must have its own position, size, alignment, and layer; do not place two elements "
|
|
189
|
+
"at the same coordinates. Preserve readable contrast and leave breathing room between all elements. "
|
|
190
|
+
"Before finishing, check that no text, number, or icon intersects another element."
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
180
194
|
def run_direct_canva_thumbnail(
|
|
181
195
|
*,
|
|
182
196
|
title: str,
|
|
@@ -231,11 +245,12 @@ def run_direct_canva_thumbnail(
|
|
|
231
245
|
"query": (
|
|
232
246
|
"Create one editable YouTube thumbnail in 16:9 format for this topic: "
|
|
233
247
|
f"{title}. Context: {topic}. Thumbnail prompt: {prompt[:1200]}. "
|
|
234
|
-
"Use clearly editable headline text boxes, a strong background,
|
|
235
|
-
"background shapes, relevant icons, depth/effects, and high-contrast lettering. "
|
|
248
|
+
"Use clearly editable headline text boxes as independent headline and secondary text boxes, a strong background, "
|
|
249
|
+
"readable text background shapes, relevant icons, depth/effects, and high-contrast lettering. "
|
|
250
|
+
f"{_thumbnail_layout_instructions()} "
|
|
236
251
|
"Follow these local Thumbnail Blueprint rules: " + blueprint_text
|
|
237
252
|
),
|
|
238
|
-
"user_intent": "Create an editable YouTube thumbnail with text boxes and visual elements when searched designs are not editable.",
|
|
253
|
+
"user_intent": "Create an editable YouTube thumbnail with separate non-overlapping text boxes, number/stat boxes and visual elements when searched designs are not editable.",
|
|
239
254
|
}))
|
|
240
255
|
job_id = _generation_job_id(generated)
|
|
241
256
|
generated_candidates = _items(generated)
|
|
@@ -208,3 +208,53 @@ def fetch_openai_compatible_models(
|
|
|
208
208
|
except ValueError as exc:
|
|
209
209
|
raise ModelDiscoveryError("O endpoint devolveu uma resposta que não é JSON.") from exc
|
|
210
210
|
return normalize_model_ids(payload)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def fetch_replicate_models(
|
|
214
|
+
api_key: str,
|
|
215
|
+
base_url: str,
|
|
216
|
+
*,
|
|
217
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
218
|
+
) -> list[str]:
|
|
219
|
+
"""Fetch Replicate model slugs with their latest version IDs.
|
|
220
|
+
|
|
221
|
+
Replicate is not OpenAI-compatible: its catalog uses ``results`` rather
|
|
222
|
+
than ``data`` and the latest version ID is required by predictions.
|
|
223
|
+
"""
|
|
224
|
+
token = str(api_key or "").strip()
|
|
225
|
+
if not token:
|
|
226
|
+
raise ModelDiscoveryError("Informe a API key Replicate antes de consultar os modelos.")
|
|
227
|
+
base = _normalise_http_base_url(base_url, error_type=ModelDiscoveryError)
|
|
228
|
+
endpoint = base if base.endswith("/models") else f"{base}/models"
|
|
229
|
+
try:
|
|
230
|
+
response = requests.get(
|
|
231
|
+
endpoint,
|
|
232
|
+
headers={"Accept": "application/json", "Authorization": f"Bearer {token}"},
|
|
233
|
+
timeout=timeout,
|
|
234
|
+
)
|
|
235
|
+
except requests.RequestException as exc:
|
|
236
|
+
raise ModelDiscoveryError(f"Não foi possível consultar {endpoint}: {exc}") from exc
|
|
237
|
+
if response.status_code >= 400:
|
|
238
|
+
if response.status_code in (401, 403):
|
|
239
|
+
raise ModelDiscoveryError(f"O endpoint Replicate recusou a API key (HTTP {response.status_code}).")
|
|
240
|
+
raise ModelDiscoveryError(f"O endpoint Replicate devolveu HTTP {response.status_code}.")
|
|
241
|
+
try:
|
|
242
|
+
payload = response.json()
|
|
243
|
+
except ValueError as exc:
|
|
244
|
+
raise ModelDiscoveryError("O endpoint Replicate devolveu uma resposta que não é JSON.") from exc
|
|
245
|
+
entries = payload.get("results") if isinstance(payload, dict) else None
|
|
246
|
+
if not isinstance(entries, list):
|
|
247
|
+
raise ModelDiscoveryError("A resposta Replicate não tem uma lista results válida.")
|
|
248
|
+
models: set[str] = set()
|
|
249
|
+
for item in entries:
|
|
250
|
+
if not isinstance(item, dict):
|
|
251
|
+
continue
|
|
252
|
+
owner = str(item.get("owner") or "").strip()
|
|
253
|
+
name = str(item.get("name") or "").strip()
|
|
254
|
+
latest = item.get("latest_version")
|
|
255
|
+
version = str(latest.get("id") or "").strip() if isinstance(latest, dict) else ""
|
|
256
|
+
if owner and name and version:
|
|
257
|
+
models.add(f"{owner}/{name}:{version}")
|
|
258
|
+
if not models:
|
|
259
|
+
raise ModelDiscoveryError("O endpoint Replicate não devolveu modelos com versão disponível.")
|
|
260
|
+
return sorted(models, key=str.casefold)[:MAX_MODEL_IDS]
|
package/package.json
CHANGED