@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, readable text "
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.5.34",
3
+ "version": "0.5.36",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",