@danhachuel/thunderbolt 0.4.75 → 0.4.76

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.
Files changed (2) hide show
  1. package/app/main.py +66 -3
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -4371,6 +4371,22 @@ def _task_thumbnail_path(task: dict[str, Any]) -> Path | None:
4371
4371
  return None
4372
4372
 
4373
4373
 
4374
+ def _task_artifact_path(task: dict[str, Any], *names: str) -> Path | None:
4375
+ """Resolve um artefacto local, incluindo caminhos relativos e legados."""
4376
+ artifacts = task.get("artifacts") if isinstance(task.get("artifacts"), dict) else {}
4377
+ for name in names:
4378
+ raw = str(artifacts.get(name) or "").strip()
4379
+ if not raw or raw.startswith(("http://", "https://")):
4380
+ continue
4381
+ candidates = [Path(raw)]
4382
+ if not Path(raw).is_absolute():
4383
+ candidates.extend([STORAGE / raw, STORAGE / "videos" / raw])
4384
+ for candidate in candidates:
4385
+ if candidate.is_file():
4386
+ return candidate
4387
+ return None
4388
+
4389
+
4374
4390
  def _is_music_task(task: dict[str, Any]) -> bool:
4375
4391
  """Identify music pipeline tasks without conflating them with ordinary video tasks."""
4376
4392
  return bool(task.get("music_mode")) or str(task.get("style_wide") or task.get("style") or "").strip().casefold() in {"music", "música"}
@@ -4921,15 +4937,41 @@ def render_automation():
4921
4937
  st.info("Ainda não existem vídeos cadastrados.")
4922
4938
  for task in tasks:
4923
4939
  with st.container(border=True):
4924
- task_cols = st.columns([2.25, 1.65, 1.15, 1.85])
4940
+ task_cols = st.columns([2.25, 1.55, 1.05, 2.15], gap="small")
4941
+ script_path = _task_artifact_path(task, "script")
4942
+ video_path = _task_artifact_path(task, "video")
4943
+ thumbnail_path = _task_thumbnail_path(task)
4944
+ thumbnail_prompt_path = _task_artifact_path(task, "thumbnail_prompt_json")
4945
+ thumbnail_prompt = str(task.get("thumbnail_prompt") or "").strip()
4925
4946
  with task_cols[0]:
4926
- thumbnail_path = _task_thumbnail_path(task)
4927
4947
  if thumbnail_path:
4928
- st.image(thumbnail_path, width=180, caption="Thumbnail")
4948
+ st.image(str(thumbnail_path), width=180, caption="Thumbnail")
4929
4949
  else:
4930
4950
  st.caption("Thumbnail ainda não pronta")
4931
4951
  st.write(f"**{task.get('topic', 'Sem tópico')}**")
4932
4952
  st.caption(f"{task.get('channel_name', 'Canal')} · {task.get('id', '')}")
4953
+ thumbnail_download_col, prompt_download_col = st.columns(2, gap="small")
4954
+ with thumbnail_download_col:
4955
+ st.download_button(
4956
+ "Baixar Thumbnail",
4957
+ data=thumbnail_path.read_bytes() if thumbnail_path else b"",
4958
+ file_name=thumbnail_path.name if thumbnail_path else "thumbnail.png",
4959
+ mime="image/png",
4960
+ key=f"automation_download_thumbnail_{task['id']}",
4961
+ use_container_width=True,
4962
+ disabled=thumbnail_path is None,
4963
+ )
4964
+ with prompt_download_col:
4965
+ prompt_data = thumbnail_prompt_path.read_bytes() if thumbnail_prompt_path else thumbnail_prompt.encode("utf-8")
4966
+ st.download_button(
4967
+ "Baixar Thumbnail Prompt",
4968
+ data=prompt_data,
4969
+ file_name=thumbnail_prompt_path.name if thumbnail_prompt_path else "thumbnail-prompt.txt",
4970
+ mime="application/json" if thumbnail_prompt_path else "text/plain",
4971
+ key=f"automation_download_thumbnail_prompt_{task['id']}",
4972
+ use_container_width=True,
4973
+ disabled=thumbnail_prompt_path is None and not thumbnail_prompt,
4974
+ )
4933
4975
  with task_cols[1]:
4934
4976
  _render_video_task_state(task)
4935
4977
  with task_cols[2]:
@@ -4949,6 +4991,27 @@ def render_automation():
4949
4991
  if st.button("Stop", key=f"automation_stop_{task['id']}", use_container_width=True, disabled=state != "doing"):
4950
4992
  stop_task_by_user(task["id"])
4951
4993
  st.rerun()
4994
+ script_download_col, video_download_col = st.columns(2, gap="small")
4995
+ with script_download_col:
4996
+ st.download_button(
4997
+ "Baixar Roteiro",
4998
+ data=script_path.read_bytes() if script_path else b"",
4999
+ file_name=script_path.name if script_path else "roteiro.md",
5000
+ mime="text/markdown",
5001
+ key=f"automation_download_script_{task['id']}",
5002
+ use_container_width=True,
5003
+ disabled=script_path is None,
5004
+ )
5005
+ with video_download_col:
5006
+ st.download_button(
5007
+ "Baixar Vídeo",
5008
+ data=video_path.read_bytes() if video_path else b"",
5009
+ file_name=video_path.name if video_path else "video.mp4",
5010
+ mime="video/mp4",
5011
+ key=f"automation_download_video_{task['id']}",
5012
+ use_container_width=True,
5013
+ disabled=video_path is None,
5014
+ )
4952
5015
  with delete_col:
4953
5016
  confirm_delete_key = f"automation_confirm_delete_{task['id']}"
4954
5017
  if st.button("Apagar", key=f"automation_delete_{task['id']}", use_container_width=True, disabled=state == "doing"):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.75",
3
+ "version": "0.4.76",
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",