@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.
@@ -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.23
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.23 install
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.23 install
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:** configure **Automação ON** e um horário `HH:MM`; confirme a lista de vídeos cadastrados. A UI não inicia jobs em segundo plano nesta entrega.
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.23 install
108
- npx.cmd --yes @danhachuel/thunderbolt@0.2.23 doctor
109
- npx.cmd --yes @danhachuel/thunderbolt@0.2.23
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 BaseWeb dentro do mesmo campo, identificadas pelo destino e não por posição. */
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:#ff4b4b !important; }
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, {"automation_on": bool(enabled), "automation_time": schedule_time.strip()})
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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "main": "scripts/cli.mjs",
6
6
  "type": "module",