@danhachuel/thunderbolt 0.5.77 → 0.5.79
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 +141 -133
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -5161,6 +5161,40 @@ def render_thumbnails():
|
|
|
5161
5161
|
st.error(str(exc))
|
|
5162
5162
|
|
|
5163
5163
|
|
|
5164
|
+
@st.fragment(run_every=5.0)
|
|
5165
|
+
def _render_tiktok_automation_cards():
|
|
5166
|
+
st.divider()
|
|
5167
|
+
st.subheader("Vídeos cadastrados TikTok")
|
|
5168
|
+
st.caption("Esta fila mostra exclusivamente tarefas associadas a canais TikTok.")
|
|
5169
|
+
tiktok_tasks = load_automation_tasks_for_platform("tiktok")
|
|
5170
|
+
if not tiktok_tasks:
|
|
5171
|
+
st.info("Ainda não existem vídeos TikTok cadastrados.")
|
|
5172
|
+
for task in tiktok_tasks:
|
|
5173
|
+
task_id = str(task["id"])
|
|
5174
|
+
with st.container(border=True):
|
|
5175
|
+
cols = st.columns([2.4, 1.4, 1.1, 1.6])
|
|
5176
|
+
with cols[0]:
|
|
5177
|
+
thumbnail_path = _task_thumbnail_path(task)
|
|
5178
|
+
if thumbnail_path:
|
|
5179
|
+
st.image(str(thumbnail_path), width=150, caption="Thumbnail")
|
|
5180
|
+
st.write(f"**{task.get('title') or task.get('topic') or 'Vídeo TikTok'}**")
|
|
5181
|
+
st.caption(f"{task.get('channel_name') or 'Canal TikTok'} · {task_id}")
|
|
5182
|
+
with cols[1]:
|
|
5183
|
+
_render_video_task_state(task)
|
|
5184
|
+
with cols[2]:
|
|
5185
|
+
st.caption("Plataforma")
|
|
5186
|
+
st.write("TikTok")
|
|
5187
|
+
st.caption("Portrait 9:16")
|
|
5188
|
+
with cols[3]:
|
|
5189
|
+
state = str(task.get("state") or "")
|
|
5190
|
+
if st.button("Start", key=f"tiktok_automation_start_{task_id}", disabled=state not in {"to_do", "blocked", "failed"}, use_container_width=True):
|
|
5191
|
+
_start_pipeline_task(task_id, state)
|
|
5192
|
+
st.rerun()
|
|
5193
|
+
if st.button("Stop", key=f"tiktok_automation_stop_{task_id}", disabled=state != "doing", use_container_width=True):
|
|
5194
|
+
stop_task_by_user(task_id)
|
|
5195
|
+
st.rerun()
|
|
5196
|
+
|
|
5197
|
+
|
|
5164
5198
|
def render_tiktok_automation():
|
|
5165
5199
|
st.title("Automação Tiktok")
|
|
5166
5200
|
st.caption("Agendamento diário da geração por canal. A fila TikTok usa Prompt Master e mantém exclusivamente o formato Portrait 9:16.")
|
|
@@ -5219,36 +5253,113 @@ def render_tiktok_automation():
|
|
|
5219
5253
|
update_channel(channel_id, {"automation_on": bool(enabled), "automation_time": schedule_time.strip(), "default_prompt_master": prompt, "prompt_master": prompt, "platform": "tiktok", "format": automation_format, "video_aspect_ratio": "Portrait 9:16", "style_wide": "portrait", "avatar_url": avatar_url, "thumbnail_url": avatar_url})
|
|
5220
5254
|
st.success("Automação TikTok guardada.")
|
|
5221
5255
|
st.rerun()
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
st.
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5256
|
+
_render_tiktok_automation_cards()
|
|
5257
|
+
|
|
5258
|
+
@st.fragment(run_every=5.0)
|
|
5259
|
+
def _render_youtube_automation_cards():
|
|
5260
|
+
st.divider()
|
|
5261
|
+
st.subheader("Vídeos cadastrados")
|
|
5262
|
+
st.caption("Start retoma as etapas já concluídas e só gera novamente o que ainda não estiver pronto. Em tarefas falhadas ou bloqueadas, a nova tentativa lê as chaves, prioridades e configurações actualmente guardadas. Apagar remove o card da fila após confirmação e preserva os artefactos locais.")
|
|
5263
|
+
tasks = load_automation_tasks_for_platform("youtube")
|
|
5264
|
+
if not tasks:
|
|
5265
|
+
st.info("Ainda não existem vídeos cadastrados.")
|
|
5266
|
+
for task in tasks:
|
|
5267
|
+
with st.container(border=True):
|
|
5268
|
+
task_cols = st.columns([2.25, 1.55, 1.05, 2.15], gap="small")
|
|
5269
|
+
script_path = _task_artifact_path(task, "script")
|
|
5270
|
+
video_path = _task_artifact_path(task, "video")
|
|
5233
5271
|
thumbnail_path = _task_thumbnail_path(task)
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5272
|
+
thumbnail_prompt_path = _task_artifact_path(task, "thumbnail_prompt_json")
|
|
5273
|
+
thumbnail_prompt = str(task.get("thumbnail_prompt") or "").strip()
|
|
5274
|
+
with task_cols[0]:
|
|
5275
|
+
if thumbnail_path:
|
|
5276
|
+
st.image(str(thumbnail_path), width=180, caption="Thumbnail")
|
|
5277
|
+
else:
|
|
5278
|
+
st.caption("Thumbnail ainda não pronta")
|
|
5279
|
+
st.write(f"**{task.get('topic', 'Sem tópico')}**")
|
|
5280
|
+
st.caption(f"{task.get('channel_name', 'Canal')} · {task.get('id', '')}")
|
|
5281
|
+
thumbnail_download_col, prompt_download_col = st.columns(2, gap="small")
|
|
5282
|
+
with thumbnail_download_col:
|
|
5283
|
+
st.download_button(
|
|
5284
|
+
"Baixar Thumbnail",
|
|
5285
|
+
data=thumbnail_path.read_bytes() if thumbnail_path else b"",
|
|
5286
|
+
file_name=thumbnail_path.name if thumbnail_path else "thumbnail.png",
|
|
5287
|
+
mime="image/png",
|
|
5288
|
+
key=f"automation_download_thumbnail_{task['id']}",
|
|
5289
|
+
use_container_width=True,
|
|
5290
|
+
disabled=thumbnail_path is None,
|
|
5291
|
+
)
|
|
5292
|
+
with prompt_download_col:
|
|
5293
|
+
prompt_data = thumbnail_prompt_path.read_bytes() if thumbnail_prompt_path else thumbnail_prompt.encode("utf-8")
|
|
5294
|
+
st.download_button(
|
|
5295
|
+
"Baixar Thumbnail Prompt",
|
|
5296
|
+
data=prompt_data,
|
|
5297
|
+
file_name=thumbnail_prompt_path.name if thumbnail_prompt_path else "thumbnail-prompt.txt",
|
|
5298
|
+
mime="application/json" if thumbnail_prompt_path else "text/plain",
|
|
5299
|
+
key=f"automation_download_thumbnail_prompt_{task['id']}",
|
|
5300
|
+
use_container_width=True,
|
|
5301
|
+
disabled=thumbnail_prompt_path is None and not thumbnail_prompt,
|
|
5302
|
+
)
|
|
5303
|
+
with task_cols[1]:
|
|
5304
|
+
_render_video_task_state(task)
|
|
5305
|
+
with task_cols[2]:
|
|
5306
|
+
st.caption("Formato")
|
|
5307
|
+
st.write(_video_task_format(task))
|
|
5308
|
+
with task_cols[3]:
|
|
5309
|
+
state = str(task.get("state") or "")
|
|
5310
|
+
start_col, stop_col, delete_col = st.columns(3)
|
|
5311
|
+
with start_col:
|
|
5312
|
+
if st.button("Start", key=f"automation_start_{task['id']}", use_container_width=True, disabled=state not in {"to_do", "blocked", "failed"}):
|
|
5313
|
+
if state in {"failed", "blocked"}:
|
|
5314
|
+
retry_task_with_current_settings(task["id"])
|
|
5315
|
+
else:
|
|
5316
|
+
transition_task(task["id"], "doing")
|
|
5317
|
+
st.rerun()
|
|
5318
|
+
with stop_col:
|
|
5319
|
+
if st.button("Stop", key=f"automation_stop_{task['id']}", use_container_width=True, disabled=state != "doing"):
|
|
5320
|
+
stop_task_by_user(task["id"])
|
|
5321
|
+
st.rerun()
|
|
5322
|
+
script_download_col, video_download_col = st.columns(2, gap="small")
|
|
5323
|
+
with script_download_col:
|
|
5324
|
+
st.download_button(
|
|
5325
|
+
"Baixar Roteiro",
|
|
5326
|
+
data=script_path.read_bytes() if script_path else b"",
|
|
5327
|
+
file_name=script_path.name if script_path else "roteiro.md",
|
|
5328
|
+
mime="text/markdown",
|
|
5329
|
+
key=f"automation_download_script_{task['id']}",
|
|
5330
|
+
use_container_width=True,
|
|
5331
|
+
disabled=script_path is None,
|
|
5332
|
+
)
|
|
5333
|
+
with video_download_col:
|
|
5334
|
+
st.download_button(
|
|
5335
|
+
"Baixar Vídeo",
|
|
5336
|
+
data=video_path.read_bytes() if video_path else b"",
|
|
5337
|
+
file_name=video_path.name if video_path else "video.mp4",
|
|
5338
|
+
mime="video/mp4",
|
|
5339
|
+
key=f"automation_download_video_{task['id']}",
|
|
5340
|
+
use_container_width=True,
|
|
5341
|
+
disabled=video_path is None,
|
|
5342
|
+
)
|
|
5343
|
+
with delete_col:
|
|
5344
|
+
confirm_delete_key = f"automation_confirm_delete_{task['id']}"
|
|
5345
|
+
if st.button("Apagar", key=f"automation_delete_{task['id']}", use_container_width=True, disabled=state == "doing"):
|
|
5346
|
+
st.session_state[confirm_delete_key] = True
|
|
5347
|
+
st.rerun()
|
|
5348
|
+
if st.session_state.get(confirm_delete_key):
|
|
5349
|
+
st.warning("Remover este vídeo da fila? Os ficheiros de artefactos serão preservados.")
|
|
5350
|
+
confirm_col, cancel_col = st.columns(2)
|
|
5351
|
+
with confirm_col:
|
|
5352
|
+
if st.button("Confirmar", key=f"automation_confirm_delete_button_{task['id']}", use_container_width=True, type="primary"):
|
|
5353
|
+
try:
|
|
5354
|
+
delete_task(task["id"])
|
|
5355
|
+
st.session_state.pop(confirm_delete_key, None)
|
|
5356
|
+
st.rerun()
|
|
5357
|
+
except ValueError as exc:
|
|
5358
|
+
st.error(str(exc))
|
|
5359
|
+
with cancel_col:
|
|
5360
|
+
if st.button("Cancelar", key=f"automation_cancel_delete_{task['id']}", use_container_width=True):
|
|
5361
|
+
st.session_state.pop(confirm_delete_key, None)
|
|
5362
|
+
st.rerun()
|
|
5252
5363
|
|
|
5253
5364
|
|
|
5254
5365
|
def render_automation():
|
|
@@ -5336,110 +5447,7 @@ def render_automation():
|
|
|
5336
5447
|
st.success("Agendamento guardado.")
|
|
5337
5448
|
st.rerun()
|
|
5338
5449
|
|
|
5339
|
-
|
|
5340
|
-
st.subheader("Vídeos cadastrados")
|
|
5341
|
-
st.caption("Start retoma as etapas já concluídas e só gera novamente o que ainda não estiver pronto. Em tarefas falhadas ou bloqueadas, a nova tentativa lê as chaves, prioridades e configurações actualmente guardadas. Apagar remove o card da fila após confirmação e preserva os artefactos locais.")
|
|
5342
|
-
tasks = load_automation_tasks_for_platform("youtube")
|
|
5343
|
-
if not tasks:
|
|
5344
|
-
st.info("Ainda não existem vídeos cadastrados.")
|
|
5345
|
-
for task in tasks:
|
|
5346
|
-
with st.container(border=True):
|
|
5347
|
-
task_cols = st.columns([2.25, 1.55, 1.05, 2.15], gap="small")
|
|
5348
|
-
script_path = _task_artifact_path(task, "script")
|
|
5349
|
-
video_path = _task_artifact_path(task, "video")
|
|
5350
|
-
thumbnail_path = _task_thumbnail_path(task)
|
|
5351
|
-
thumbnail_prompt_path = _task_artifact_path(task, "thumbnail_prompt_json")
|
|
5352
|
-
thumbnail_prompt = str(task.get("thumbnail_prompt") or "").strip()
|
|
5353
|
-
with task_cols[0]:
|
|
5354
|
-
if thumbnail_path:
|
|
5355
|
-
st.image(str(thumbnail_path), width=180, caption="Thumbnail")
|
|
5356
|
-
else:
|
|
5357
|
-
st.caption("Thumbnail ainda não pronta")
|
|
5358
|
-
st.write(f"**{task.get('topic', 'Sem tópico')}**")
|
|
5359
|
-
st.caption(f"{task.get('channel_name', 'Canal')} · {task.get('id', '')}")
|
|
5360
|
-
thumbnail_download_col, prompt_download_col = st.columns(2, gap="small")
|
|
5361
|
-
with thumbnail_download_col:
|
|
5362
|
-
st.download_button(
|
|
5363
|
-
"Baixar Thumbnail",
|
|
5364
|
-
data=thumbnail_path.read_bytes() if thumbnail_path else b"",
|
|
5365
|
-
file_name=thumbnail_path.name if thumbnail_path else "thumbnail.png",
|
|
5366
|
-
mime="image/png",
|
|
5367
|
-
key=f"automation_download_thumbnail_{task['id']}",
|
|
5368
|
-
use_container_width=True,
|
|
5369
|
-
disabled=thumbnail_path is None,
|
|
5370
|
-
)
|
|
5371
|
-
with prompt_download_col:
|
|
5372
|
-
prompt_data = thumbnail_prompt_path.read_bytes() if thumbnail_prompt_path else thumbnail_prompt.encode("utf-8")
|
|
5373
|
-
st.download_button(
|
|
5374
|
-
"Baixar Thumbnail Prompt",
|
|
5375
|
-
data=prompt_data,
|
|
5376
|
-
file_name=thumbnail_prompt_path.name if thumbnail_prompt_path else "thumbnail-prompt.txt",
|
|
5377
|
-
mime="application/json" if thumbnail_prompt_path else "text/plain",
|
|
5378
|
-
key=f"automation_download_thumbnail_prompt_{task['id']}",
|
|
5379
|
-
use_container_width=True,
|
|
5380
|
-
disabled=thumbnail_prompt_path is None and not thumbnail_prompt,
|
|
5381
|
-
)
|
|
5382
|
-
with task_cols[1]:
|
|
5383
|
-
_render_video_task_state(task)
|
|
5384
|
-
with task_cols[2]:
|
|
5385
|
-
st.caption("Formato")
|
|
5386
|
-
st.write(_video_task_format(task))
|
|
5387
|
-
with task_cols[3]:
|
|
5388
|
-
state = str(task.get("state") or "")
|
|
5389
|
-
start_col, stop_col, delete_col = st.columns(3)
|
|
5390
|
-
with start_col:
|
|
5391
|
-
if st.button("Start", key=f"automation_start_{task['id']}", use_container_width=True, disabled=state not in {"to_do", "blocked", "failed"}):
|
|
5392
|
-
if state in {"failed", "blocked"}:
|
|
5393
|
-
retry_task_with_current_settings(task["id"])
|
|
5394
|
-
else:
|
|
5395
|
-
transition_task(task["id"], "doing")
|
|
5396
|
-
st.rerun()
|
|
5397
|
-
with stop_col:
|
|
5398
|
-
if st.button("Stop", key=f"automation_stop_{task['id']}", use_container_width=True, disabled=state != "doing"):
|
|
5399
|
-
stop_task_by_user(task["id"])
|
|
5400
|
-
st.rerun()
|
|
5401
|
-
script_download_col, video_download_col = st.columns(2, gap="small")
|
|
5402
|
-
with script_download_col:
|
|
5403
|
-
st.download_button(
|
|
5404
|
-
"Baixar Roteiro",
|
|
5405
|
-
data=script_path.read_bytes() if script_path else b"",
|
|
5406
|
-
file_name=script_path.name if script_path else "roteiro.md",
|
|
5407
|
-
mime="text/markdown",
|
|
5408
|
-
key=f"automation_download_script_{task['id']}",
|
|
5409
|
-
use_container_width=True,
|
|
5410
|
-
disabled=script_path is None,
|
|
5411
|
-
)
|
|
5412
|
-
with video_download_col:
|
|
5413
|
-
st.download_button(
|
|
5414
|
-
"Baixar Vídeo",
|
|
5415
|
-
data=video_path.read_bytes() if video_path else b"",
|
|
5416
|
-
file_name=video_path.name if video_path else "video.mp4",
|
|
5417
|
-
mime="video/mp4",
|
|
5418
|
-
key=f"automation_download_video_{task['id']}",
|
|
5419
|
-
use_container_width=True,
|
|
5420
|
-
disabled=video_path is None,
|
|
5421
|
-
)
|
|
5422
|
-
with delete_col:
|
|
5423
|
-
confirm_delete_key = f"automation_confirm_delete_{task['id']}"
|
|
5424
|
-
if st.button("Apagar", key=f"automation_delete_{task['id']}", use_container_width=True, disabled=state == "doing"):
|
|
5425
|
-
st.session_state[confirm_delete_key] = True
|
|
5426
|
-
st.rerun()
|
|
5427
|
-
if st.session_state.get(confirm_delete_key):
|
|
5428
|
-
st.warning("Remover este vídeo da fila? Os ficheiros de artefactos serão preservados.")
|
|
5429
|
-
confirm_col, cancel_col = st.columns(2)
|
|
5430
|
-
with confirm_col:
|
|
5431
|
-
if st.button("Confirmar", key=f"automation_confirm_delete_button_{task['id']}", use_container_width=True, type="primary"):
|
|
5432
|
-
try:
|
|
5433
|
-
delete_task(task["id"])
|
|
5434
|
-
st.session_state.pop(confirm_delete_key, None)
|
|
5435
|
-
st.rerun()
|
|
5436
|
-
except ValueError as exc:
|
|
5437
|
-
st.error(str(exc))
|
|
5438
|
-
with cancel_col:
|
|
5439
|
-
if st.button("Cancelar", key=f"automation_cancel_delete_{task['id']}", use_container_width=True):
|
|
5440
|
-
st.session_state.pop(confirm_delete_key, None)
|
|
5441
|
-
st.rerun()
|
|
5442
|
-
|
|
5450
|
+
_render_youtube_automation_cards()
|
|
5443
5451
|
|
|
5444
5452
|
def render_upload_direct():
|
|
5445
5453
|
st.subheader("Upload directo")
|
package/package.json
CHANGED