@danhachuel/thunderbolt 0.5.91 → 0.5.93
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 +14 -20
- package/package.json +1 -1
- package/scripts/cli.mjs +8 -11
- package/seed/references/guide-instagram.md +0 -2
- package/seed/references/guide-supabase.md +0 -2
- package/seed/references/tutorial-youtube-data-api-key.md +0 -5
- package/seed/references/youtube-video-upload-frontend.md +0 -4
package/app/main.py
CHANGED
|
@@ -4874,12 +4874,16 @@ def _render_video_task_state(task: dict[str, Any]) -> None:
|
|
|
4874
4874
|
st.caption(f"Manifesto: {task.get('video_result')}")
|
|
4875
4875
|
|
|
4876
4876
|
|
|
4877
|
-
def _start_pipeline_task(task_id: str, state: str) ->
|
|
4877
|
+
def _start_pipeline_task(task_id: str, state: str) -> bool:
|
|
4878
4878
|
"""Start or retry a video task from any platform automation card."""
|
|
4879
4879
|
if state in {"blocked", "failed"}:
|
|
4880
|
-
retry_task_with_current_settings(task_id)
|
|
4880
|
+
updated = retry_task_with_current_settings(task_id)
|
|
4881
4881
|
else:
|
|
4882
|
-
transition_task(task_id, "doing")
|
|
4882
|
+
updated = transition_task(task_id, "doing")
|
|
4883
|
+
if not updated:
|
|
4884
|
+
st.error("Não foi possível iniciar este vídeo: a tarefa já não existe na fila.")
|
|
4885
|
+
return False
|
|
4886
|
+
return True
|
|
4883
4887
|
|
|
4884
4888
|
|
|
4885
4889
|
def render_videos():
|
|
@@ -5386,11 +5390,8 @@ def _render_youtube_automation_cards():
|
|
|
5386
5390
|
start_col, stop_col, delete_col = st.columns(3)
|
|
5387
5391
|
with start_col:
|
|
5388
5392
|
if st.button("Start", key=f"automation_start_{task['id']}", use_container_width=True, disabled=state not in {"to_do", "blocked", "failed"}):
|
|
5389
|
-
if
|
|
5390
|
-
|
|
5391
|
-
else:
|
|
5392
|
-
transition_task(task["id"], "doing")
|
|
5393
|
-
st.rerun()
|
|
5393
|
+
if _start_pipeline_task(str(task["id"]), state):
|
|
5394
|
+
st.rerun()
|
|
5394
5395
|
with stop_col:
|
|
5395
5396
|
if st.button("Stop", key=f"automation_stop_{task['id']}", use_container_width=True, disabled=state != "doing"):
|
|
5396
5397
|
stop_task_by_user(task["id"])
|
|
@@ -8340,17 +8341,15 @@ def render_notifications():
|
|
|
8340
8341
|
|
|
8341
8342
|
|
|
8342
8343
|
def render_models_ai_tutorial():
|
|
8343
|
-
tutorial_url = "https://github.com/gyoridavid/ai_agents_az/blob/main/episode_8/guide-instagram.md"
|
|
8344
8344
|
tutorial_path = ROOT / "seed" / "references" / "guide-instagram.md"
|
|
8345
8345
|
st.title("Tutorial Meta")
|
|
8346
8346
|
st.caption("Guia de configuração de uma conta Instagram profissional e das credenciais Meta para automações com n8n.")
|
|
8347
|
-
st.markdown(f"[Abrir fonte original no GitHub]({tutorial_url})")
|
|
8348
8347
|
try:
|
|
8349
8348
|
tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
|
|
8350
8349
|
except OSError:
|
|
8351
8350
|
tutorial_content = ""
|
|
8352
8351
|
if not tutorial_content:
|
|
8353
|
-
st.error("O conteúdo local do tutorial não está disponível.
|
|
8352
|
+
st.error("O conteúdo local do tutorial não está disponível.")
|
|
8354
8353
|
return
|
|
8355
8354
|
st.markdown(tutorial_content, unsafe_allow_html=True)
|
|
8356
8355
|
|
|
@@ -8362,18 +8361,16 @@ def render_niche_tutorial(tutorial_kind: str):
|
|
|
8362
8361
|
|
|
8363
8362
|
|
|
8364
8363
|
def render_supabase_tutorial():
|
|
8365
|
-
tutorial_url = "https://github.com/gyoridavid/ai_agents_az/blob/main/episode_8/guide-supabase.md"
|
|
8366
8364
|
tutorial_path = ROOT / "seed" / "references" / "guide-supabase.md"
|
|
8367
8365
|
ui_language = current_ui_language()
|
|
8368
8366
|
st.title(ui_text("Tutorial Supabase", ui_language))
|
|
8369
8367
|
st.caption(ui_text("Guia de configuração do Supabase para automações com n8n.", ui_language))
|
|
8370
|
-
st.markdown(f"[Abrir fonte original no GitHub]({tutorial_url})")
|
|
8371
8368
|
try:
|
|
8372
8369
|
tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
|
|
8373
8370
|
except OSError:
|
|
8374
8371
|
tutorial_content = ""
|
|
8375
8372
|
if not tutorial_content:
|
|
8376
|
-
st.error("O conteúdo local do tutorial não está disponível.
|
|
8373
|
+
st.error("O conteúdo local do tutorial não está disponível.")
|
|
8377
8374
|
return
|
|
8378
8375
|
st.markdown(tutorial_content, unsafe_allow_html=True)
|
|
8379
8376
|
|
|
@@ -8383,13 +8380,12 @@ def render_google_oauth_tutorial():
|
|
|
8383
8380
|
tutorial_path = ROOT / "seed" / "references" / "tutorial-oauth-google.md"
|
|
8384
8381
|
st.title("Tutorial OAuth do Google")
|
|
8385
8382
|
st.caption("Guia completo para configurar a autenticação OAuth do Google e o acesso à YouTube Data API v3.")
|
|
8386
|
-
st.markdown("[Abrir Google Cloud Console](https://console.cloud.google.com/)")
|
|
8387
8383
|
try:
|
|
8388
8384
|
tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
|
|
8389
8385
|
except OSError:
|
|
8390
8386
|
tutorial_content = ""
|
|
8391
8387
|
if not tutorial_content:
|
|
8392
|
-
st.error("O conteúdo local do tutorial não está disponível.
|
|
8388
|
+
st.error("O conteúdo local do tutorial não está disponível.")
|
|
8393
8389
|
return
|
|
8394
8390
|
st.markdown(tutorial_content, unsafe_allow_html=False)
|
|
8395
8391
|
|
|
@@ -8399,13 +8395,12 @@ def render_youtube_frontend_upload_tutorial():
|
|
|
8399
8395
|
tutorial_path = ROOT / "seed" / "references" / "youtube-video-upload-frontend.md"
|
|
8400
8396
|
st.title("Tutorial YouTube Video-Upload Frontend")
|
|
8401
8397
|
st.caption("Guia prático e seguro para rever metadados, sessão e envio directo de vídeos concluídos.")
|
|
8402
|
-
st.markdown("[Abrir referência técnica no GitHub](https://github.com/Nojus10/YouTube-Video-Upload-Frontend-Api)")
|
|
8403
8398
|
try:
|
|
8404
8399
|
tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
|
|
8405
8400
|
except OSError:
|
|
8406
8401
|
tutorial_content = ""
|
|
8407
8402
|
if not tutorial_content:
|
|
8408
|
-
st.error("O conteúdo local do tutorial não está disponível.
|
|
8403
|
+
st.error("O conteúdo local do tutorial não está disponível.")
|
|
8409
8404
|
return
|
|
8410
8405
|
st.markdown(tutorial_content, unsafe_allow_html=False)
|
|
8411
8406
|
|
|
@@ -8415,13 +8410,12 @@ def render_youtube_data_api_key_tutorial():
|
|
|
8415
8410
|
tutorial_path = ROOT / "seed" / "references" / "tutorial-youtube-data-api-key.md"
|
|
8416
8411
|
st.title("Tutorial YouTube Data API Key (Public Data)")
|
|
8417
8412
|
st.caption("Guia para criar e configurar uma YouTube Data API Key para consultas de dados públicos.")
|
|
8418
|
-
st.markdown("[Abrir documento fonte no Google Drive](https://drive.google.com/file/d/1ypzaR1v8khBxyUQmZ12ZAY0pGTD49mqt/view)")
|
|
8419
8413
|
try:
|
|
8420
8414
|
tutorial_content = tutorial_path.read_text(encoding="utf-8").strip()
|
|
8421
8415
|
except OSError:
|
|
8422
8416
|
tutorial_content = ""
|
|
8423
8417
|
if not tutorial_content:
|
|
8424
|
-
st.error("O conteúdo local do tutorial não está disponível.
|
|
8418
|
+
st.error("O conteúdo local do tutorial não está disponível.")
|
|
8425
8419
|
return
|
|
8426
8420
|
st.markdown(tutorial_content, unsafe_allow_html=False)
|
|
8427
8421
|
|
package/package.json
CHANGED
package/scripts/cli.mjs
CHANGED
|
@@ -322,7 +322,7 @@ function stopPipelineWorker() {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
function startPipelineWorker() {
|
|
325
|
-
if (shuttingDown || pipelineWorker
|
|
325
|
+
if (shuttingDown || pipelineWorker) return;
|
|
326
326
|
pipelineWorker = spawn(python, ["-m", "hermes_ui.pipeline_worker"], {
|
|
327
327
|
cwd: root,
|
|
328
328
|
stdio: "inherit",
|
|
@@ -334,19 +334,16 @@ function startPipelineWorker() {
|
|
|
334
334
|
pipelineWorker = null;
|
|
335
335
|
if (shuttingDown) return;
|
|
336
336
|
console.error(`Thunderbolt pipeline worker: terminou (código ${code ?? "-"}, sinal ${signal ?? "-"}).`);
|
|
337
|
-
|
|
338
|
-
pipelineRestartTimer =
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}, 5000);
|
|
342
|
-
}
|
|
337
|
+
pipelineRestartTimer = setTimeout(() => {
|
|
338
|
+
pipelineRestartTimer = null;
|
|
339
|
+
startPipelineWorker();
|
|
340
|
+
}, 5000);
|
|
343
341
|
});
|
|
344
342
|
}
|
|
345
343
|
|
|
346
344
|
function monitorWorkers() {
|
|
347
345
|
if (shuttingDown) return;
|
|
348
|
-
|
|
349
|
-
else if (pipelineWorker) stopPipelineWorker();
|
|
346
|
+
startPipelineWorker();
|
|
350
347
|
startAutomationWorker();
|
|
351
348
|
}
|
|
352
349
|
|
|
@@ -396,6 +393,6 @@ const stopWorker = () => {
|
|
|
396
393
|
process.on("SIGINT", stopWorker);
|
|
397
394
|
process.on("SIGTERM", stopWorker);
|
|
398
395
|
monitorWorkers();
|
|
399
|
-
// O
|
|
400
|
-
//
|
|
396
|
+
// O pipeline worker permanece disponível para recolher imediatamente tarefas
|
|
397
|
+
// colocadas em `doing` pelo botão Start, mesmo quando a fila estava vazia.
|
|
401
398
|
workerMonitorTimer = setInterval(monitorWorkers, 2000);
|
|
@@ -171,5 +171,3 @@ You'll see this interface below.
|
|
|
171
171
|
2. The Instagram account id which you'll need to set in the `Configure` node in the n8n workflow
|
|
172
172
|
|
|
173
173
|
---
|
|
174
|
-
|
|
175
|
-
> **Source:** [ai_agents_az/episode_8/guide-instagram.md](https://github.com/gyoridavid/ai_agents_az/blob/main/episode_8/guide-instagram.md)
|
|
@@ -65,5 +65,3 @@ No Thunderbolt, abra **Configurações > Configuração API > AI Influencers > B
|
|
|
65
65
|
O Thunderbolt usa o Supabase como backend seleccionado, não como executor de n8n: os assets são enviados ao Storage, os metadados ficam nas tabelas e os estados de geração ficam em `influencer_content`. Para uma execução totalmente local, seleccione **SQLite** no mesmo painel; a alternativa cria `storage/state/ai_influencers.db` e não utiliza a conta Supabase.
|
|
66
66
|
|
|
67
67
|
A conta Supabase está pronta para ser utilizada pela automação.
|
|
68
|
-
|
|
69
|
-
Fonte original: [guide-supabase.md no GitHub](https://github.com/gyoridavid/ai_agents_az/blob/main/episode_8/guide-supabase.md)
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
# Tutorial YouTube Data API Key (Public Data)
|
|
2
|
-
|
|
3
1
|
Este tutorial explica como criar uma **YouTube Data API Key** no Google Cloud e configurá-la no Thunderbolt para consultar dados públicos do YouTube, como vídeos, canais e estatísticas públicas.
|
|
4
2
|
|
|
5
|
-
> **Fonte:** [Tutorial YouTube Data API Key (Public Data) no Google Drive](https://drive.google.com/file/d/1ypzaR1v8khBxyUQmZ12ZAY0pGTD49mqt/view)
|
|
6
|
-
|
|
7
3
|
## Quando usar uma API Key
|
|
8
4
|
|
|
9
5
|
Uma API Key é suficiente para operações com dados públicos, como pesquisar vídeos, consultar canais públicos e obter estatísticas públicas. Ela **não concede acesso a dados privados** da conta nem substitui o OAuth 2.0 para playlists privadas, inscrições privadas ou uploads.
|
|
@@ -93,4 +89,3 @@ Uma API Key só identifica o projecto e permite chamadas autorizadas para dados
|
|
|
93
89
|
- [Google Cloud Console](https://console.cloud.google.com/)
|
|
94
90
|
- [YouTube Data API v3](https://developers.google.com/youtube/v3)
|
|
95
91
|
- [Documentação de quotas da YouTube Data API](https://developers.google.com/youtube/v3/determine_quota_cost)
|
|
96
|
-
- [Documento fonte no Google Drive](https://drive.google.com/file/d/1ypzaR1v8khBxyUQmZ12ZAY0pGTD49mqt/view)
|
|
@@ -42,7 +42,3 @@ Depois de confirmar o canal, a privacidade e os metadados, seleccione o fluxo de
|
|
|
42
42
|
| Falha de rede | Não repita automaticamente sem confirmar se o vídeo foi criado no YouTube Studio. |
|
|
43
43
|
| Metadados incompletos | Corrija título, descrição, tags, idioma ou privacidade no formulário antes de enviar. |
|
|
44
44
|
| Limite ou política da plataforma | Pare o processo e siga as políticas e limites actualmente aplicáveis do YouTube. |
|
|
45
|
-
|
|
46
|
-
## Referência técnica
|
|
47
|
-
|
|
48
|
-
O material fornecido pelo utilizador cita o repositório [Nojus10/YouTube-Video-Upload-Frontend-Api](https://github.com/Nojus10/YouTube-Video-Upload-Frontend-Api). O Thunderbolt apresenta este tutorial como orientação de integração segura; não replica segredos, valores de autenticação nem instruções de extracção de credenciais.
|