@danhachuel/thunderbolt 0.4.28 → 0.4.30

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
@@ -6135,6 +6135,25 @@ def _persist_media_cards(settings: dict[str, Any], cards: list[dict[str, Any]],
6135
6135
  return updated
6136
6136
 
6137
6137
 
6138
+ def _fetch_media_models(card: dict[str, Any]) -> list[str]:
6139
+ """Consultar modelos do provider, incluindo o catálogo nativo Gemini."""
6140
+ provider = str(card.get("provider") or "").strip().lower()
6141
+ if provider == "nano_banana":
6142
+ base_url = str(card.get("base_url") or "https://generativelanguage.googleapis.com/v1beta").rstrip("/")
6143
+ response = requests.get(
6144
+ f"{base_url}/models",
6145
+ params={"key": str(card.get("api_key") or "").strip()},
6146
+ headers={"Accept": "application/json"},
6147
+ timeout=12,
6148
+ )
6149
+ response.raise_for_status()
6150
+ payload = response.json()
6151
+ models = payload.get("models") if isinstance(payload, dict) else []
6152
+ return sorted({str(item.get("name", "")).removeprefix("models/") for item in models if isinstance(item, dict) and item.get("name")}, key=str.casefold)
6153
+ from integrations.openai_model_discovery import fetch_openai_compatible_models
6154
+ return fetch_openai_compatible_models(str(card.get("api_key") or ""), str(card.get("base_url") or ""))
6155
+
6156
+
6138
6157
  def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str, Any]], index: int, *, embedded: bool = False) -> None:
6139
6158
  card = normalize_media_card(cards[index], index)
6140
6159
  cards[index] = card
@@ -6160,7 +6179,23 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
6160
6179
  st.caption("Este provider não exige API key.")
6161
6180
  api_key = ""
6162
6181
  with model_col:
6163
- model = st.text_input("Modelo", value=str(card.get("model") or ""), help="ID do modelo ou rota usada pelo provider.", key=f"media_card_{card_id}_model")
6182
+ model_catalog = st.session_state.get(f"media_model_catalog_{card_id}", [])
6183
+ if not isinstance(model_catalog, list):
6184
+ model_catalog = []
6185
+ current_model = str(card.get("model") or "").strip()
6186
+ discovered_models = [str(item).strip() for item in model_catalog if str(item).strip()]
6187
+ options = ["__select_model__", *list(dict.fromkeys(discovered_models))]
6188
+ if current_model and current_model not in options:
6189
+ options.insert(1, current_model)
6190
+ selected_model = st.selectbox(
6191
+ "Modelo",
6192
+ options,
6193
+ index=options.index(current_model) if current_model in options else 0,
6194
+ format_func=lambda value: "Seleccione um modelo" if value == "__select_model__" else value,
6195
+ help="Consulte o catálogo do provider e seleccione um modelo disponível.",
6196
+ key=f"media_card_{card_id}_model_select",
6197
+ )
6198
+ model = "" if selected_model == "__select_model__" else selected_model
6164
6199
  base_url = st.text_input("Base URL", value=str(card.get("base_url") or definition.default_base_url), key=f"media_card_{card_id}_base_url")
6165
6200
  extra_values: dict[str, str] = {}
6166
6201
  if definition.extra_fields:
@@ -6177,17 +6212,26 @@ def _render_media_provider_card(settings: dict[str, Any], cards: list[dict[str,
6177
6212
  supports_video = st.checkbox("Pool Vídeo", value=bool(card.get("supports_video", definition.supports_video)), key=f"media_card_{card_id}_video")
6178
6213
  with status_cols[3]:
6179
6214
  priority = st.number_input("Prioridade", min_value=0, max_value=999, value=int(card.get("priority", index)), step=1, key=f"media_card_{card_id}_priority")
6180
- action_cols = st.columns(3)
6215
+ action_cols = st.columns(4)
6181
6216
  with action_cols[0]:
6182
- test_clicked = st.form_submit_button("Testar Chamada API", use_container_width=True, key=f"media_card_{card_id}_test")
6217
+ refresh_clicked = st.form_submit_button("Consultar Modelos", use_container_width=True, key=f"media_card_{card_id}_refresh")
6183
6218
  with action_cols[1]:
6184
- save_clicked = st.form_submit_button("Salvar", type="primary", use_container_width=True, key=f"media_card_{card_id}_save")
6219
+ test_clicked = st.form_submit_button("Testar Chamada API", use_container_width=True, key=f"media_card_{card_id}_test")
6185
6220
  with action_cols[2]:
6221
+ save_clicked = st.form_submit_button("Salvar", type="primary", use_container_width=True, key=f"media_card_{card_id}_save")
6222
+ with action_cols[3]:
6186
6223
  remove_clicked = st.form_submit_button("Remover provider", use_container_width=True, key=f"media_card_{card_id}_remove")
6187
6224
  edited = dict(card)
6188
6225
  edited.update({"api_key": str(api_key or "").strip(), "model": str(model or "").strip(), "base_url": str(base_url or "").strip(), "enabled": bool(enabled), "supports_image": bool(supports_image), "supports_video": bool(supports_video), "priority": int(priority), **extra_values})
6189
6226
  cards[index] = edited
6190
- if test_clicked:
6227
+ if refresh_clicked:
6228
+ try:
6229
+ discovered = _fetch_media_models(edited)
6230
+ st.session_state[f"media_model_catalog_{card_id}"] = discovered
6231
+ st.success(f"{len(discovered)} modelo(s) disponíveis neste endpoint.")
6232
+ except Exception:
6233
+ st.error("Não foi possível consultar os modelos deste provider. Confirme a API key e a Base URL.")
6234
+ elif test_clicked:
6191
6235
  result = test_media_provider_card(edited)
6192
6236
  edited["test_result"] = stamp_test_result(result)
6193
6237
  _persist_media_cards(settings, cards, str(settings.get(MEDIA_IMAGE_ACTIVE_CARD_KEY) or ""), str(settings.get(MEDIA_VIDEO_ACTIVE_CARD_KEY) or ""))
@@ -10,8 +10,9 @@ from typing import Any
10
10
  from .creative_generation import generate_thumbnail_prompt
11
11
  from .media_generation import generate_image_from_pool
12
12
  from .media_providers import media_cards_for_pool
13
- from .storage import STORAGE, now, read_json, update_json
13
+ from .storage import STORAGE, ensure_storage, now, read_json, update_json
14
14
  from .thumbnail_generation import ThumbnailGenerationError, generate_thumbnail_image
15
+ from .thumbnail_blueprints import thumbnail_blueprint_for_channel
15
16
 
16
17
 
17
18
  def _generate_image_with_pool(
@@ -290,12 +291,19 @@ def regenerate_thumbnail_prompt(
290
291
  topic = record["topic"] or record["title"]
291
292
  if not topic:
292
293
  raise ThumbnailGenerationError("A tarefa não tem tópico para refazer o prompt da thumbnail.")
294
+ ensure_storage()
295
+ visual_blueprint = thumbnail_blueprint_for_channel(
296
+ {**(channel or {}), "thumbnail_blueprint_id": record.get("thumbnail_blueprint_id") or (channel or {}).get("thumbnail_blueprint_id", "")}
297
+ )
298
+ effective_blueprint = {**(blueprint or {})}
299
+ if visual_blueprint.get("content"):
300
+ effective_blueprint["thumbnail_blueprint_rules"] = visual_blueprint["content"]
293
301
  variant = generate_thumbnail_prompt(
294
302
  settings,
295
303
  channel or {},
296
304
  topic,
297
305
  current_prompt=record["prompt"],
298
- blueprint=blueprint,
306
+ blueprint=effective_blueprint,
299
307
  language=language,
300
308
  )
301
309
  updated = _update_thumbnail_task(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
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",