@danhachuel/thunderbolt 0.6.83 → 0.6.85

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.
@@ -115,6 +115,19 @@ def _provider_label(card: Mapping[str, Any]) -> str:
115
115
  return f"{definition.label} · {model}"
116
116
 
117
117
 
118
+ def _ugc_duration_seconds(value: str) -> int:
119
+ """Parse the UGC duration field as minutes:seconds with a safe default."""
120
+ try:
121
+ minutes_text, seconds_text = str(value or "1:20").strip().split(":", 1)
122
+ minutes = max(0, int(minutes_text))
123
+ seconds = int(seconds_text)
124
+ if 0 <= seconds < 60:
125
+ return max(1, min(3600, minutes * 60 + seconds))
126
+ except (TypeError, ValueError):
127
+ pass
128
+ return 80
129
+
130
+
118
131
  def _content_metadata(item: Mapping[str, Any]) -> dict[str, Any]:
119
132
  raw = item.get("metadata") if isinstance(item.get("metadata"), Mapping) else item.get("metadata_json")
120
133
  if isinstance(raw, Mapping):
@@ -445,7 +458,7 @@ def render_motion_control(settings: dict[str, Any]) -> None:
445
458
  def render_ugc_products(settings: dict[str, Any]) -> None:
446
459
  """Create portrait UGC photos or videos from a product and a saved character."""
447
460
  st.title("UGC Products")
448
- st.caption("Crie fotos ou vídeos UGC em formato vertical 9:16 para TikTok e Instagram usando um produto e um personagem cadastrados.")
461
+ st.caption("Crie fotos ou vídeos UGC em formato vertical 9:16 para TikTok e Instagram usando um produto e um personagem cadastrado.")
449
462
  repository = _repository(settings)
450
463
  if repository is None:
451
464
  return
@@ -457,15 +470,21 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
457
470
 
458
471
  with media_tab:
459
472
  if not influencers:
460
- influencers = [{"id": "", "name": "Sem personagem", "bio": ""}]
473
+ influencers = [{"id": "", "name": "", "bio": ""}]
461
474
  influencer_options = _influencer_options(influencers)
475
+ if not influencer_options:
476
+ influencer_options = [""]
462
477
  selected_influencer = st.selectbox(
463
- "Personagem (opcional)",
478
+ "Personagem",
464
479
  influencer_options,
465
480
  format_func=lambda value: _influencer_label(influencers, value),
466
481
  key="ugc_products_character",
467
482
  )
468
- selected_character = next(item for item in influencers if str(item.get("id")) == selected_influencer)
483
+ selected_character = next(
484
+ (item for item in influencers if str(item.get("id") or "") == str(selected_influencer or "")),
485
+ influencers[0],
486
+ )
487
+ selected_influencer = str(selected_character.get("id") or "")
469
488
  try:
470
489
  character_assets = repository.list_assets(selected_influencer)
471
490
  except Exception:
@@ -480,16 +499,26 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
480
499
  key="ugc_products_inputs",
481
500
  help="Anexe uma ou mais referências do produto. Para geração, a primeira imagem será usada como referência visual principal.",
482
501
  )
483
- generation_mode = st.radio("Geração", ["Gerar Foto", "Gerar Video"], horizontal=True, key="ugc_products_generation_mode")
484
- duration = st.number_input("Duração de cada clip (segundos)", min_value=2, max_value=30, value=8, step=1, key="ugc_products_duration", disabled=generation_mode != "Gerar Video")
502
+ generation_col, duration_col = st.columns([2, 1])
503
+ with generation_col:
504
+ generation_mode = st.selectbox("Tipo de geração", ["Gerar Foto", "Gerar Video"], key="ugc_products_generation_mode")
505
+ duration_text = "1:20"
506
+ if generation_mode == "Gerar Video":
507
+ with duration_col:
508
+ duration_text = st.text_input("Duração de cada clip (MM:SS)", value="1:20", max_chars=5, key="ugc_products_duration_text", width=120)
509
+ duration = _ugc_duration_seconds(duration_text)
485
510
  image_cards, image_options = _provider_options(settings, "image")
486
511
  video_cards = _workflow_provider_cards(settings, provider=None)
487
512
  video_options = [str(card.get("id") or "") for card in video_cards]
488
- image_provider_id = st.selectbox("Provider / modelo de imagem", image_options, format_func=lambda value: _provider_label(next(card for card in image_cards if str(card.get("id")) == value)), key="ugc_products_image_provider") if image_cards else ""
489
- video_provider_id = st.selectbox("Provider / modelo de vídeo", video_options, format_func=lambda value: _provider_label(next(card for card in video_cards if str(card.get("id")) == value)), key="ugc_products_video_provider") if video_cards else ""
513
+ image_provider_id = ""
514
+ video_provider_id = ""
515
+ if generation_mode == "Gerar Foto":
516
+ image_provider_id = st.selectbox("Provider / modelo de imagem", image_options, format_func=lambda value: _provider_label(next(card for card in image_cards if str(card.get("id")) == value)), key="ugc_products_image_provider") if image_cards else ""
517
+ else:
518
+ video_provider_id = st.selectbox("Provider / modelo de vídeo", video_options, format_func=lambda value: _provider_label(next(card for card in video_cards if str(card.get("id")) == value)), key="ugc_products_video_provider") if video_cards else ""
490
519
  script = st.text_area("Roteiro de vídeo (UGC)", height=150, key="ugc_products_script", placeholder="O roteiro gerado pela IA aparecerá aqui; também pode editá-lo antes de gerar.")
491
520
  generate_script = st.button("Gerar roteiro com IA", use_container_width=True, key="ugc_products_generate_script")
492
- generate = st.button("Gerar Foto" if generation_mode == "Gerar Foto" else "Gerar Video", type="primary", use_container_width=True, key="ugc_products_generate")
521
+ generate = st.button("Gerar Mídia (Foto/Vídeo)", type="primary", use_container_width=True, key="ugc_products_generate")
493
522
  if generate_script:
494
523
  context = "\n".join(
495
524
  item for item in [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.83",
3
+ "version": "0.6.85",
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",