@danhachuel/thunderbolt 0.2.23 → 0.2.25
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/MANUAL-INSTALACAO.md +5 -5
- package/README.md +5 -5
- package/app/main.py +73 -5
- package/hermes_ui/domain.py +12 -0
- package/package.json +1 -1
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Este manual descreve a instalação local da UI Thunderbolt, baseada no MoneyPrinterTurbo, utilizando o pacote npm `@danhachuel/thunderbolt`. O fluxo recomendado instala automaticamente o ambiente Python, as dependências da aplicação, as dependências do MoneyPrinterTurbo, o Streamlit e o suporte FFmpeg através de `imageio-ffmpeg`.
|
|
4
4
|
|
|
5
|
-
> **Versão deste manual:** 0.2.
|
|
5
|
+
> **Versão deste manual:** 0.2.25
|
|
6
6
|
> **Pacote npm:** `@danhachuel/thunderbolt`
|
|
7
7
|
> **Porta padrão da UI:** `localhost:3030`
|
|
8
8
|
> **Repositório:** [github.com/DanHachuel/thunderbolt](https://github.com/DanHachuel/thunderbolt)
|
|
@@ -100,13 +100,13 @@ Execute:
|
|
|
100
100
|
Windows PowerShell ou MobaXterm:
|
|
101
101
|
|
|
102
102
|
```powershell
|
|
103
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
103
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.25 install
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
Linux/macOS:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
npx --yes @danhachuel/thunderbolt@0.2.
|
|
109
|
+
npx --yes @danhachuel/thunderbolt@0.2.25 install
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
A instalação normal é **segura para actualizações**: preserva `storage`, Blueprints, Brandings, configurações e artefactos do utilizador. Remove apenas `.venv`, o clone técnico do MoneyPrinterTurbo e dependências que serão recriadas. Uma pasta antiga sem dados do utilizador, como `C:\Users\<utilizador>\AppData\Local\hermes` da tentativa incompleta, pode ser removida; uma pasta antiga que contenha Blueprints, Brandings ou storage é preservada e apenas avisada no terminal. Feche processos Python, Node, Streamlit e MobaXterm que estejam a usar as pastas antes de executar.
|
|
@@ -368,7 +368,7 @@ Após iniciar a aplicação, valide o seguinte percurso:
|
|
|
368
368
|
8. **Limpador de metadado:** suba um vídeo de terceiro, preencha título, preview, links, timestamps e tags, aplique a limpeza e descarregue a cópia limpa e o manifesto JSON. O original fica preservado e vídeos da aba Novo vídeo não são aceites nesta área.
|
|
369
369
|
9. **Configurações:** confirme que os caminhos e credenciais estão locais e não aparecem no Git.
|
|
370
370
|
10. **Novo vídeo:** confirme que `Pexels/Pixabay` substitui o label antigo, que `Estilo IA` aparece apenas em `full_ia` e que `Apenas Música` exige áudio e guarda `background_mode=none`.
|
|
371
|
-
11. **Automação:**
|
|
371
|
+
11. **Automação:** no cartão de cada canal, escolha o **Blueprint padrão** e a **Voz padrão**, clique em `Guardar` e confirme que o resumo do cartão se actualiza. Configure também **Automação ON** e um horário `HH:MM`; confirme a lista de vídeos cadastrados. Os mesmos defaults aparecem em **Canais** e são usados em novas tarefas. A UI não inicia jobs em segundo plano nesta entrega.
|
|
372
372
|
12. **Teste de vozes:** em Configurações, teste Edge/Azure ou provider configurado e confirme reprodução/download sem criação de tarefa.
|
|
373
373
|
13. **Upload directo:** configure manualmente cookies, sessionInfo e INNERTUBE_API_KEY, atribua o `DELEGATED_SESSION_ID` no canal e teste apenas com um vídeo de validação. O método envia chunks de 256 KiB e não extrai cookies do navegador.
|
|
374
374
|
14. **MCP:** confirme que Short Video Maker, AutoVio, OpenMontage e OpenCut aparecem com as portas padrão editáveis. O estado **Activo** é apenas uma preferência local; a detecção deve indicar **Não detectado** quando os serviços externos não estiverem instalados ou iniciados.
|
|
@@ -378,7 +378,7 @@ Após iniciar a aplicação, valide o seguinte percurso:
|
|
|
378
378
|
|
|
379
379
|
O modo `Pexels/Pixabay` representa materiais de stock. Ao seleccionar `full_ia`, o selector `Estilo IA` apresenta os 12 estilos disponíveis. Ao seleccionar `Apenas Música`, a UI obriga a escolher uma música existente, carregar um ficheiro ou solicitar uma música a um endpoint Suno configurado. O vídeo recebe `background_mode=none`, sem fundo Pexels/Pixabay ou IA.
|
|
380
380
|
|
|
381
|
-
A aba **Automação** lista os canais e vídeos, guarda **Automação ON** e valida horários diários no formato `HH:MM`. É apenas uma camada de UI nesta versão, sem execução de worker.
|
|
381
|
+
A aba **Automação** lista os canais e vídeos, permite escolher e guardar **Blueprint padrão** e **Voz padrão** por canal, guarda **Automação ON** e valida horários diários no formato `HH:MM`. Os valores ficam sincronizados com o editor existente no cartão da aba **Canais** e são copiados para novas tarefas. É apenas uma camada de UI nesta versão, sem execução de worker.
|
|
382
382
|
|
|
383
383
|
A área **Teste de vozes**, dentro de Configurações, é isolada da pipeline. O preview pode ser reproduzido e descarregado de `storage/voice_previews/`.
|
|
384
384
|
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ A primeira versão implementa a camada UI independente com:
|
|
|
16
16
|
| Brandings | Subaba própria dentro de Blueprints, upload/listagem de Brandings e criação conjunta com Blueprint |
|
|
17
17
|
| Canais | Subabas de importação pública sem API Key, Data API opcional e cadastro manual independente |
|
|
18
18
|
| Novo vídeo | Subabas Criar vídeo e Vídeos; lotes; 51 rótulos de idioma; Pexels/Pixabay, full IA com Estilo IA e Apenas Música com agente musical |
|
|
19
|
-
| Automação | Lista de vídeos e canais, Automação ON e horário diário HH:MM; UI configurável sem worker em segundo plano |
|
|
19
|
+
| Automação | Lista de vídeos e canais, selectores editáveis de Blueprint/voz padrão, Automação ON e horário diário HH:MM; UI configurável sem worker em segundo plano |
|
|
20
20
|
| Upload | YouTube via `youtube-automation-agent` adaptado internamente, OAuth directo de redundância, Upload directo experimental, TikTok, Instagram e Facebook Pages no front end |
|
|
21
21
|
| MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
|
|
22
22
|
| Limpador de metadado | Upload isolado de vídeos terceiros, limpeza FFmpeg, edição de título/descrição/tags e manifesto JSON |
|
|
@@ -104,9 +104,9 @@ thunderbolt
|
|
|
104
104
|
No Windows PowerShell, se `npx` for bloqueado por `npx.ps1`, use directamente `npx.cmd`:
|
|
105
105
|
|
|
106
106
|
```powershell
|
|
107
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
108
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
109
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
107
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.25 install
|
|
108
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.25 doctor
|
|
109
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.25
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
Como alternativa, pode permitir scripts para o seu utilizador:
|
|
@@ -185,7 +185,7 @@ A subaba **Upload directo** adapta o [YouTube-Video-Upload-Frontend-Api](https:/
|
|
|
185
185
|
|
|
186
186
|
Em **Novo vídeo**, o estilo visual `Pexels/Pixabay` é o modo de materiais, `full_ia` abre o selector **Estilo IA** com os 12 estilos solicitados e **Apenas Música** exige um áudio local, um upload musical ou um pedido ao endpoint Suno configurado. Neste último modo, a task fica com `background_mode=none`: não são gerados fundos Pexels/Pixabay nem fundos IA.
|
|
187
187
|
|
|
188
|
-
O agente musical guarda os ficheiros em `storage/music/`, aceita formatos de áudio comuns e pode descarregar uma URL de áudio devolvida por um endpoint Suno compatível. A aba **Automação** guarda `Automação ON` e um horário diário `HH:MM` por canal e lista os vídeos cadastrados; por definição, esta entrega não executa workers de fundo.
|
|
188
|
+
O agente musical guarda os ficheiros em `storage/music/`, aceita formatos de áudio comuns e pode descarregar uma URL de áudio devolvida por um endpoint Suno compatível. Em **Canais**, abra **Definir Blueprint e voz padrão** no cartão do canal para guardar os defaults; em **Automação**, os mesmos dois selectores aparecem no cartão e são sincronizados. Esses valores são usados automaticamente em novas tarefas criadas para o canal. A aba **Automação** também guarda `Automação ON` e um horário diário `HH:MM` por canal e lista os vídeos cadastrados; por definição, esta entrega não executa workers de fundo.
|
|
189
189
|
|
|
190
190
|
## Teste de vozes
|
|
191
191
|
|
package/app/main.py
CHANGED
|
@@ -16,7 +16,7 @@ try:
|
|
|
16
16
|
except (OSError, json.JSONDecodeError):
|
|
17
17
|
APP_VERSION = ""
|
|
18
18
|
|
|
19
|
-
from hermes_ui.domain import STAGES, create_batch, create_channel, create_tasks_for_batch, delete_channel, pipeline_summary, transition_task, update_channel
|
|
19
|
+
from hermes_ui.domain import STAGES, create_batch, create_channel, create_tasks_for_batch, delete_channel, pipeline_summary, set_channel_defaults, transition_task, update_channel
|
|
20
20
|
from hermes_ui.storage import BLUEPRINTS, ensure_storage, list_blueprint_files, load_blueprint_file, now, read_json, write_json
|
|
21
21
|
from hermes_ui.blueprints import create_blueprint_from_link, list_branding_files, save_generated_blueprint
|
|
22
22
|
from hermes_ui.metadata_cleaner import build_description, clean_video_metadata, list_edit_records, metadata_manifest, normalize_tags, save_edit_record, store_external_video
|
|
@@ -129,9 +129,18 @@ st.markdown("""
|
|
|
129
129
|
.content-value { color:#f4f8fb; font-size:1.8rem; font-weight:700; margin-top:.3rem; }
|
|
130
130
|
.stage { border-left:3px solid #35a7ff; padding:.65rem .8rem; margin:.4rem 0; background:#101d2a; border-radius:8px; }
|
|
131
131
|
.small-muted { color:#8ba6bb; font-size:.85rem; }
|
|
132
|
-
/* Cores dos chips
|
|
132
|
+
/* Cores dos chips por identidade da plataforma, sem depender da ordem de selecção. */
|
|
133
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag] { color:#ffffff !important; border:0 !important; font-weight:700 !important; }
|
|
134
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag] span[title],
|
|
135
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag] button,
|
|
136
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag] svg { color:#ffffff !important; fill:#ffffff !important; }
|
|
137
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag][aria-label="YouTube"] { background:#ff0000 !important; }
|
|
138
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag][aria-label="TikTok"] { background:#000000 !important; }
|
|
139
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag][aria-label="Instagram"] { background:#e1306c !important; }
|
|
140
|
+
[data-testid="stMultiSelectTagsContainer"] span[data-tag][aria-label="Facebook Pages"] { background:#1877f2 !important; }
|
|
141
|
+
/* Compatibilidade com versões BaseWeb que usam data-baseweb=tag. */
|
|
133
142
|
[data-testid="stMultiSelect"] [data-baseweb="tag"] { color:#ffffff !important; border:0 !important; font-weight:700 !important; }
|
|
134
|
-
[data-testid="stMultiSelect"] [data-baseweb="tag"]:has(button[aria-label*="YouTube"]) { background:#
|
|
143
|
+
[data-testid="stMultiSelect"] [data-baseweb="tag"]:has(button[aria-label*="YouTube"]) { background:#ff0000 !important; }
|
|
135
144
|
[data-testid="stMultiSelect"] [data-baseweb="tag"]:has(button[aria-label*="TikTok"]) { background:#000000 !important; }
|
|
136
145
|
[data-testid="stMultiSelect"] [data-baseweb="tag"]:has(button[aria-label*="Instagram"]) { background:#e1306c !important; }
|
|
137
146
|
[data-testid="stMultiSelect"] [data-baseweb="tag"]:has(button[aria-label*="Facebook Pages"]) { background:#1877f2 !important; }
|
|
@@ -179,6 +188,20 @@ def voice_catalog(current: str = "") -> list[str]:
|
|
|
179
188
|
return voices
|
|
180
189
|
|
|
181
190
|
|
|
191
|
+
def channel_default_options(channel: dict) -> tuple[list[str], dict[str, str], str, list[str], str]:
|
|
192
|
+
"""Return synchronised Blueprint and voice options for a channel editor."""
|
|
193
|
+
blueprint_items = blueprint_catalog()
|
|
194
|
+
blueprint_ids = [item[0] for item in blueprint_items]
|
|
195
|
+
blueprint_labels = {item[0]: item[1] for item in blueprint_items}
|
|
196
|
+
current_blueprint = str(channel.get("default_blueprint_id") or channel.get("blueprint_id") or "")
|
|
197
|
+
if current_blueprint and current_blueprint not in blueprint_ids:
|
|
198
|
+
blueprint_ids.append(current_blueprint)
|
|
199
|
+
blueprint_labels[current_blueprint] = current_blueprint
|
|
200
|
+
current_voice = str(channel.get("default_voice") or channel.get("voice") or "")
|
|
201
|
+
voice_options = voice_catalog(current_voice)
|
|
202
|
+
return blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice
|
|
203
|
+
|
|
204
|
+
|
|
182
205
|
def render_dashboard():
|
|
183
206
|
st.title("Thunderbolt")
|
|
184
207
|
st.caption("Interface local para operação e automação de conteúdo faceless")
|
|
@@ -488,6 +511,29 @@ def render_channels():
|
|
|
488
511
|
if st.button("Cancelar", key=f"cancel_delete_{channel['id']}", use_container_width=True):
|
|
489
512
|
st.session_state.pop(delete_key, None)
|
|
490
513
|
st.rerun()
|
|
514
|
+
blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
|
|
515
|
+
with st.expander("Definir Blueprint e voz padrão", expanded=False):
|
|
516
|
+
editor_cols = st.columns(2)
|
|
517
|
+
with editor_cols[0]:
|
|
518
|
+
channel_blueprint = st.selectbox(
|
|
519
|
+
"Blueprint padrão",
|
|
520
|
+
blueprint_ids,
|
|
521
|
+
index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0,
|
|
522
|
+
format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"),
|
|
523
|
+
key=f"channel_default_blueprint_{channel['id']}",
|
|
524
|
+
)
|
|
525
|
+
with editor_cols[1]:
|
|
526
|
+
channel_voice = st.selectbox(
|
|
527
|
+
"Voz padrão",
|
|
528
|
+
voice_options,
|
|
529
|
+
index=voice_options.index(current_voice) if current_voice in voice_options else 0,
|
|
530
|
+
format_func=lambda item: item or "Sem voz padrão",
|
|
531
|
+
key=f"channel_default_voice_{channel['id']}",
|
|
532
|
+
)
|
|
533
|
+
if st.button("Guardar Blueprint e voz", key=f"save_channel_defaults_{channel['id']}", type="primary"):
|
|
534
|
+
set_channel_defaults(channel["id"], channel_blueprint, channel_voice)
|
|
535
|
+
st.success("Blueprint padrão e voz padrão guardados para este canal.")
|
|
536
|
+
st.rerun()
|
|
491
537
|
|
|
492
538
|
|
|
493
539
|
def render_new_video():
|
|
@@ -624,7 +670,25 @@ def render_automation():
|
|
|
624
670
|
with cols[1]:
|
|
625
671
|
st.write(f"**{channel.get('name', 'Sem nome')}**")
|
|
626
672
|
st.caption(channel.get("handle") or channel.get("url") or "sem URL")
|
|
627
|
-
st.caption(f"Blueprint: {channel.get('default_blueprint_id') or channel.get('blueprint_id') or '—'} · Voz: {channel.get('default_voice') or channel.get('voice') or '—'}")
|
|
673
|
+
st.caption(f"Blueprint actual: {channel.get('default_blueprint_id') or channel.get('blueprint_id') or '—'} · Voz actual: {channel.get('default_voice') or channel.get('voice') or '—'}")
|
|
674
|
+
blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
|
|
675
|
+
default_cols = st.columns(2)
|
|
676
|
+
with default_cols[0]:
|
|
677
|
+
automation_blueprint = st.selectbox(
|
|
678
|
+
"Blueprint padrão",
|
|
679
|
+
blueprint_ids,
|
|
680
|
+
index=blueprint_ids.index(current_blueprint) if current_blueprint in blueprint_ids else 0,
|
|
681
|
+
format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"),
|
|
682
|
+
key=f"automation_blueprint_{channel_id}",
|
|
683
|
+
)
|
|
684
|
+
with default_cols[1]:
|
|
685
|
+
automation_voice = st.selectbox(
|
|
686
|
+
"Voz padrão",
|
|
687
|
+
voice_options,
|
|
688
|
+
index=voice_options.index(current_voice) if current_voice in voice_options else 0,
|
|
689
|
+
format_func=lambda item: item or "Sem voz padrão",
|
|
690
|
+
key=f"automation_voice_{channel_id}",
|
|
691
|
+
)
|
|
628
692
|
with cols[2]:
|
|
629
693
|
enabled = st.toggle("Automação ON", value=bool(channel.get("automation_on", False)), key=f"automation_on_{channel_id}")
|
|
630
694
|
with cols[3]:
|
|
@@ -634,7 +698,11 @@ def render_automation():
|
|
|
634
698
|
if not valid_hhmm(schedule_time):
|
|
635
699
|
st.error("Use o formato HH:MM, por exemplo 08:30.")
|
|
636
700
|
else:
|
|
637
|
-
update_channel(channel_id, {
|
|
701
|
+
update_channel(channel_id, {
|
|
702
|
+
"automation_on": bool(enabled),
|
|
703
|
+
"automation_time": schedule_time.strip(),
|
|
704
|
+
})
|
|
705
|
+
set_channel_defaults(channel_id, automation_blueprint, automation_voice)
|
|
638
706
|
st.success("Agendamento guardado.")
|
|
639
707
|
st.rerun()
|
|
640
708
|
|
package/hermes_ui/domain.py
CHANGED
|
@@ -66,6 +66,18 @@ def update_channel(channel_id: str, updates: dict[str, Any]) -> dict[str, Any] |
|
|
|
66
66
|
return None
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
def set_channel_defaults(channel_id: str, blueprint_id: str = "", voice: str = "") -> dict[str, Any] | None:
|
|
70
|
+
"""Persist the canonical and legacy aliases for a channel's generation defaults."""
|
|
71
|
+
blueprint = str(blueprint_id or "").strip()
|
|
72
|
+
selected_voice = str(voice or "").strip()
|
|
73
|
+
return update_channel(channel_id, {
|
|
74
|
+
"blueprint_id": blueprint,
|
|
75
|
+
"default_blueprint_id": blueprint,
|
|
76
|
+
"voice": selected_voice,
|
|
77
|
+
"default_voice": selected_voice,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
|
|
69
81
|
def delete_channel(channel_id: str) -> dict[str, Any] | None:
|
|
70
82
|
"""Remove apenas o cadastro do canal, preservando tarefas e artefactos."""
|
|
71
83
|
channels = read_json("channels.json", [])
|
package/package.json
CHANGED