@danhachuel/thunderbolt 0.6.80 → 0.6.81

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.
@@ -28,7 +28,6 @@ from hermes_ui.media_generation import (
28
28
  generate_motion_control_video,
29
29
  generate_ugc_product_video,
30
30
  generate_video_for_card,
31
- upload_kie_file,
32
31
  validate_motion_control_file,
33
32
  )
34
33
  from hermes_ui.creative_generation import generate_ugc_segment_prompts
@@ -349,8 +348,12 @@ def _workflow_owner(repository: Any) -> str:
349
348
  return ensure_standalone_content_owner(repository)
350
349
 
351
350
 
352
- def _workflow_provider_cards(settings: Mapping[str, Any]) -> list[dict[str, Any]]:
353
- return [card for card in media_cards_for_pool(settings, "video") if str(card.get("provider") or "").strip().lower() == "kie_ai"]
351
+ def _workflow_provider_cards(settings: Mapping[str, Any], *, provider: str | None = "kie_ai") -> list[dict[str, Any]]:
352
+ cards = media_cards_for_pool(settings, "video")
353
+ if provider is None:
354
+ return cards
355
+ normalized_provider = str(provider).strip().lower()
356
+ return [card for card in cards if str(card.get("provider") or "").strip().lower() == normalized_provider]
354
357
 
355
358
 
356
359
  def render_motion_control(settings: dict[str, Any]) -> None:
@@ -421,18 +424,17 @@ def render_motion_control(settings: dict[str, Any]) -> None:
421
424
 
422
425
 
423
426
  def render_ugc_products(settings: dict[str, Any]) -> None:
424
- """Render the local product-image to VEO3 workflow without Telegram or social publishing."""
427
+ """Render the local product-image to video workflow without Telegram or social publishing."""
425
428
  st.title("UGC Products")
426
- st.caption("Crie um anúncio UGC a partir de uma imagem de produto e de um roteiro local. O workflow gera dois clips VEO3 de 8 segundos e junta-os com FFmpeg no storage local.")
429
+ st.caption("Crie um anúncio UGC a partir de uma imagem de produto e de um roteiro local. O workflow gera dois clips de vídeo com o provider e modelo seleccionados e junta-os com FFmpeg no storage local.")
427
430
  repository = _repository(settings)
428
431
  if repository is None:
429
432
  return
430
- video_cards = [card for card in _workflow_provider_cards(settings) if str(card.get("model") or "").strip().lower() in {"", "veo3", "veo3_fast", "veo3_lite"}]
433
+ video_cards = _workflow_provider_cards(settings, provider=None)
431
434
  if not video_cards:
432
- st.warning("Active e configure um cartão KIE AI com modelo VEO3 compatível no pool de vídeo em Configuração API > API Keys > Imagem e Video IA.")
435
+ st.warning("Active e configure pelo menos um provider de vídeo no pool de vídeo em Configuração API > API Keys > Imagem e Video IA.")
433
436
  return
434
437
  image_cards, _ = _provider_options(settings, "image")
435
- image_cards = [card for card in image_cards if str(card.get("provider") or "").strip().lower() == "nano_banana"]
436
438
  with st.form("influencer_ugc_products_form"):
437
439
  product_upload = st.file_uploader("Imagem do produto", type=["jpg", "jpeg", "png", "webp"], key="ugc_products_image")
438
440
  script = st.text_area("Roteiro de vídeo", height=180, placeholder="Escreva a demonstração, falas e acções. Para controlar os dois clips manualmente, separe-os com uma linha contendo ---.", key="ugc_products_script")
@@ -444,7 +446,7 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
444
446
  elif improve_image:
445
447
  st.info("Não existe um provider activo no pool de imagem; será usada a imagem original.")
446
448
  card_options = [str(card.get("id") or "") for card in video_cards]
447
- provider_id = st.selectbox("Provider VEO3", card_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")
449
+ provider_id = st.selectbox("Provider / modelo de vídeo", card_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")
448
450
  generate = st.form_submit_button("Gerar UGC Product", type="primary", use_container_width=True)
449
451
  if generate:
450
452
  if product_upload is None:
@@ -467,15 +469,15 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
467
469
  "prompt": script,
468
470
  "caption": "",
469
471
  "provider": card.get("provider"),
470
- "model": str(card.get("model") or "veo3_fast"),
472
+ "model": str(card.get("model") or ""),
471
473
  "platform": "",
472
474
  "state": "running",
473
475
  "metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "telegram": False, "social_publish": False},
474
476
  }
475
477
  )
476
- with st.spinner("A enviar a imagem para KIE, a gerar os dois clips VEO3 e a juntá-los localmente…"):
478
+ image_url = _image_input({"stored_path": str(image_path), "mime_type": mimetypes.guess_type(image_path.name)[0] or "image/jpeg"})
479
+ with st.spinner("A gerar os dois clips com o provider seleccionado e a juntá-los localmente…"):
477
480
  try:
478
- image_url = upload_kie_file(image_path, card, upload_path="thunderbolt/ugc-products")
479
481
  output, task_ids = generate_ugc_product_video(settings, card, image_url=image_url, prompts=prompts)
480
482
  repository.update_content(record["id"], {"state": "completed", "artifact_path": str(output), "provider_request_id": ",".join(task_ids), "metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "task_ids": task_ids, "telegram": False, "social_publish": False}})
481
483
  record_notification("influencer_content_completed", "UGC Products concluído", "O vídeo UGC Product foi gerado, unido e guardado localmente.", metadata={"content_id": record["id"], "workflow": "ugc_products", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:completed")
@@ -1102,7 +1102,7 @@ def generate_ugc_product_video(
1102
1102
  prompts: list[str],
1103
1103
  output_path: Path | None = None,
1104
1104
  ) -> tuple[Path, list[str]]:
1105
- """Generate two 8-second KIE VEO3 clips and concatenate them locally."""
1105
+ """Generate two 8-second clips with the selected video card and concatenate them locally."""
1106
1106
  clean_prompts = [str(item or "").strip() for item in prompts if str(item or "").strip()]
1107
1107
  if len(clean_prompts) != 2:
1108
1108
  raise MediaGenerationError("UGC Products requer exactamente dois prompts de segmento.")
@@ -1111,6 +1111,6 @@ def generate_ugc_product_video(
1111
1111
  segment_paths = [destination.with_name(f"{destination.stem}-segment-{index + 1}.mp4") for index in range(2)]
1112
1112
  task_ids: list[str] = []
1113
1113
  for prompt, segment_path in zip(clean_prompts, segment_paths):
1114
- _, task_id = generate_ugc_segment(settings, card, image_url=image_url, prompt=prompt, output_path=segment_path, duration=8)
1115
- task_ids.append(task_id)
1114
+ generate_video_for_card(settings, card, prompt, image_url=image_url, output_path=segment_path)
1115
+ task_ids.append(segment_path.stem)
1116
1116
  return concatenate_video_files(segment_paths, destination), task_ids
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.80",
3
+ "version": "0.6.81",
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",