@danhachuel/thunderbolt 0.3.49 → 0.3.50

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.3.49
5
+ > **Versão deste manual:** 0.3.50
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 --prefer-online @danhachuel/thunderbolt@0.3.49 install
103
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.50 install
104
104
  ```
105
105
 
106
106
  Linux/macOS:
107
107
 
108
108
  ```bash
109
- npx --yes --prefer-online @danhachuel/thunderbolt@0.3.49 install
109
+ npx --yes --prefer-online @danhachuel/thunderbolt@0.3.50 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.
@@ -114,7 +114,7 @@ A instalação normal é **segura para actualizações**: preserva `storage`, Bl
114
114
  Se quiser apagar absolutamente tudo de forma intencional, use o comando destrutivo separado:
115
115
 
116
116
  ```powershell
117
- npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.49 install --purge-data
117
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.50 install --purge-data
118
118
  ```
119
119
 
120
120
  O parâmetro `--purge-data` apaga Blueprints, Brandings, configurações, storage e artefactos locais. Não o use numa actualização normal.
@@ -359,7 +359,7 @@ Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
359
359
 
360
360
  ### 4.1 Pools de LLM, imagem e vídeo
361
361
 
362
- Em **Configuração API > API Keys**, o expander **LLM — providers e modelos** continua a guardar cartões independentes para os providers textuais. O campo **Prioridade** substitui o antigo checkbox **LLM principal**: o cartão com valor **1** é tentado primeiro, depois o **2**, o **3** e assim sucessivamente. Pode manter várias API keys do mesmo provider em cartões distintos; a geração textual percorre o pool por essa ordem e tenta a prioridade seguinte apenas em quota, timeout, erro de transporte ou falha transitória. A chave interna de cartão activo permanece apenas como espelho de compatibilidade das instalações antigas.
362
+ Em **Configuração API > API Keys**, o expander **LLM — providers e modelos** continua a guardar cartões independentes para os providers textuais. O campo **Prioridade** substitui o antigo checkbox **LLM principal**: o cartão com valor **1** é tentado primeiro, depois o **2**, o **3** e assim sucessivamente. Pode manter várias API keys do mesmo provider em cartões distintos; a geração textual percorre o pool por essa ordem e tenta a prioridade seguinte apenas em quota, timeout, erro de transporte ou falha transitória. Quando **LLM Telegram** está marcado, o campo Prioridade fica desactivado e o cartão é excluído do pool textual normal, sendo usado exclusivamente para notificações Telegram. A chave interna de cartão activo permanece apenas como espelho de compatibilidade das instalações antigas.
363
363
 
364
364
  Dentro do expander **LLM — providers e modelos**, acima dos cartões de providers, está o card **Limite LLM NVIDIA NIM**. Ele permite ligar **Activar limitador NVIDIA NIM — 40 RPM**. O valor padrão é 40 pedidos numa janela de 60 segundos. O contador é persistido localmente por cartão/chave e aplica-se à criação manual, aos workers e às automações. A opção vem desligada para não alterar instalações existentes; ligue-a quando o endpoint activo for NVIDIA NIM e a sua conta tiver esse limite.
365
365
 
@@ -524,7 +524,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
524
524
 
525
525
  Os parâmetros da UI são número de clusters entre 2 e 10, suporte mínimo entre 0,01 e 0,50, país, engagement, intervalo de datas e tags, todos dentro da área principal da aba. O núcleo normaliza os dados, calcula engagement, aplica filtros, faz transformação logarítmica e standardização, executa K-Means e calcula itemsets/regras com FP-Growth. Não são apresentados resultados até ao primeiro clique em **Analisar Nichos**; o mesmo botão aplica alterações posteriores aos filtros. Os resultados são DataFrames de clusters, itemsets frequentes, regras de associação e dados analisados; o gráfico de dispersão é criado nativamente com Plotly.
526
526
 
527
- As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.49 install`; o instalador detecta e reutiliza o que já estiver válido.
527
+ As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.50 install`; o instalador detecta e reutiliza o que já estiver válido.
528
528
 
529
529
  ### Niche Finder Apify
530
530
 
package/README.md CHANGED
@@ -69,7 +69,7 @@ No topo da área principal da aplicação existe o menu nativo de idioma no padr
69
69
 
70
70
  A UI suporta os temas **Dark** e **Light** através do menu nativo de três pontos do Streamlit, no local original do toolbar. Não existe um selector Theme adicional dentro da página. A configuração distribuída em `.streamlit/config.toml` disponibiliza as variantes nomeadas **Dark** e **Light**, e o menu nativo continua responsável por alternar entre os modos, seguindo o padrão do [MoneyPrinterTurbo](https://github.com/harry0703/MoneyPrinterTurbo). O CSS próprio do Thunderbolt usa cores semânticas, `currentColor` e `color-mix` para acompanhar o tema activo, sem alterar a posição nem a funcionalidade do toolbar, do botão Deploy e do menu principal.
71
71
 
72
- ## Navegação da UI 0.3.49
72
+ ## Navegação da UI 0.3.50
73
73
 
74
74
  A barra lateral mantém os níveis principais, nesta ordem: **Início**, **Niche Finder**, **Pipeline**, **Pipeline TikTok**, **Automação**, **Edição**, **AI Influencers** e **Configurações**. **Pipeline Vídeos** é expansível e contém **Criação de Vídeos**, **Backlog Vídeos**, **Roteiros**, **Thumbnails** e **Upload**. **Pipeline Música** é expansível e contém **Criação de Músicas** e **Upload Música**. **Automação** também é expansível e contém **Automação Youtube**. **Edição** é expansível e contém **Limpador de Metadados**, **Cortes**, **Editor Python** e **Download Mídia**, nessa ordem. **AI Influencers** é expansível e contém **Personagens**, **Redes Sociais**, **Tutorial Meta** e **Tutorial Supabase**, nessa ordem. **Niche Finder** é expansível e contém **Niche Finder Kaggle** e **Niche Finder Apify**. **Configurações** é expansível e contém **Canais Youtube**, **Blueprints Youtube**, **MCP**, **Contas Google**, **Notificações**, **Logs** e **Configuração API**, nessa ordem. O Início reúne o dashboard e as filas do Pipeline, sem botões de acções rápidas.
75
75
 
@@ -87,7 +87,7 @@ A subaba **Serviços e modelos** mantém as credenciais de providers e serviços
87
87
 
88
88
  A área expansível **Voz, TTS e música — Azure Speech, restantes serviços e Suno** está organizada em cartões independentes para **Azure Speech**, **ElevenLabs**, **SiliconFlow**, **MiniMax TTS**, **Chatterbox**, **Sonilo** e **Suno**. Cada cartão contém apenas os campos do respectivo serviço e o seu próprio botão de diagnóstico; o Suno fica visualmente separado por ser um agente de criação musical, não um provider TTS.
89
89
 
90
- Nos cartões de **LLM — providers e modelos**, o campo **Prioridade** define a ordem de tentativa do pool textual. O valor **1** é usado primeiro; em quota, timeout, erro de transporte ou falha transitória, o router tenta a prioridade **2**, depois **3** e assim sucessivamente. O checkbox antigo **LLM principal** deixou de existir. O card **Limite LLM NVIDIA NIM** fica dentro deste mesmo expander, acima dos cartões de providers, e os seus controlos continuam partilhados pela UI, pipeline e automações. A chave interna de cartão activo continua apenas como espelho de compatibilidade para instalações antigas.
90
+ Nos cartões de **LLM — providers e modelos**, o campo **Prioridade** define a ordem de tentativa do pool textual. O valor **1** é usado primeiro; em quota, timeout, erro de transporte ou falha transitória, o router tenta a prioridade **2**, depois **3** e assim sucessivamente. O checkbox antigo **LLM principal** deixou de existir. O card **Limite LLM NVIDIA NIM** fica dentro deste mesmo expander, acima dos cartões de providers, e os seus controlos continuam partilhados pela UI, pipeline e automações. Quando **LLM Telegram** está marcado, o cartão fica reservado exclusivamente às notificações Telegram: o campo Prioridade é desactivado e o cartão é excluído do pool textual normal. A chave interna de cartão activo continua apenas como espelho de compatibilidade para instalações antigas.
91
91
 
92
92
  O expander **Imagem e Video** reúne cartões independentes para **Nano Banana**, **Pollinations.ai**, **Agnes AI**, **Hugging Face Inference API**, **Cloudflare Workers AI**, **InferencePort Proxy**, **阿里云 (Alibaba Cloud Model Studio)**, **KIE AI** e **FAL AI**. Cada cartão permite configurar API key/token quando necessário, modelo, Base URL, activar o provider, escolher participação no **Pool Imagem** e/ou **Pool Vídeo**, definir prioridade e executar **Testar Chamada API**. Os parâmetros técnicos de composição, como **Image Size** e **Aspect Ratio**, não são apresentados nem editáveis nos cartões: ficam como defaults internos e são acrescentados automaticamente ao prompt da geração de imagem e/ou vídeo. O InferencePort usa por defeito `http://localhost:8080/v1` e não exige chave; o Cloudflare requer Account ID; os restantes providers devem ser preenchidos de acordo com a conta e endpoint disponibilizados pelo serviço.
93
93
 
package/app/main.py CHANGED
@@ -5040,22 +5040,25 @@ def _render_llm_card(settings: dict[str, Any], cards: list[dict[str, Any]], inde
5040
5040
  with status_cols[0]:
5041
5041
  enabled = st.checkbox("Provider activo", value=bool(card.get("enabled", True)), key=f"llm_card_{card_id}_enabled")
5042
5042
  with status_cols[1]:
5043
+ telegram_llm = st.checkbox(
5044
+ "LLM Telegram",
5045
+ value=bool(card.get("telegram_llm", False)),
5046
+ help="Usar este cartão exclusivamente para o roteamento de notificações Telegram. Apenas um cartão pode ficar seleccionado.",
5047
+ key=f"llm_card_{card_id}_telegram",
5048
+ )
5049
+ with status_cols[2]:
5043
5050
  priority = st.number_input(
5044
5051
  "Prioridade",
5045
5052
  min_value=1,
5046
5053
  max_value=999,
5047
5054
  value=max(1, int(card.get("priority", index + 1))),
5048
5055
  step=1,
5049
- help="Ordem de tentativa do pool LLM: 1 é o primeiro; em falha elegível, segue para 2, 3 e assim por diante.",
5056
+ disabled=bool(telegram_llm),
5057
+ help="Não se aplica a cartões LLM Telegram. Nos restantes cartões, 1 é o primeiro; em falha elegível, segue para 2, 3 e assim por diante.",
5050
5058
  key=f"llm_card_{card_id}_priority",
5051
5059
  )
5052
- with status_cols[2]:
5053
- telegram_llm = st.checkbox(
5054
- "LLM Telegram",
5055
- value=bool(card.get("telegram_llm", False)),
5056
- help="Usar este cartão exclusivamente para o roteamento de notificações Telegram. Apenas um cartão pode ficar seleccionado.",
5057
- key=f"llm_card_{card_id}_telegram",
5058
- )
5060
+ if telegram_llm:
5061
+ st.caption("Exclusivo para Notificações de Telegram — prioridade ignorada no pool LLM.")
5059
5062
 
5060
5063
  save_clicked = st.form_submit_button("Salvar", type="primary", use_container_width=True, key=f"llm_card_{card_id}_save")
5061
5064
  remove_clicked = False
@@ -5104,7 +5107,7 @@ def render_llm_provider_cards(settings: dict[str, Any], *, embedded: bool = Fals
5104
5107
  settings.update(migrated)
5105
5108
  write_json("settings.json", settings)
5106
5109
  with st.expander("LLM — providers e modelos", expanded=False):
5107
- st.caption("Configure cada provider num cartão independente. Pode repetir o mesmo provider para manter várias API keys; a prioridade 1 é tentada primeiro e o router segue para as prioridades seguintes em falhas elegíveis.")
5110
+ st.caption("Configure cada provider num cartão independente. Pode repetir o mesmo provider para manter várias API keys; a prioridade 1 é tentada primeiro. Cartões marcados como LLM Telegram ficam excluídos do pool textual e são usados apenas pelas notificações Telegram.")
5108
5111
  with st.container(border=True):
5109
5112
  st.markdown("### Limite LLM NVIDIA NIM")
5110
5113
  st.caption("Quando ligado, limita apenas cartões cujo endpoint é integrate.api.nvidia.com a 40 pedidos por janela de 60 segundos, partilhados entre UI, pipeline e automações.")
@@ -305,9 +305,13 @@ def ensure_llm_provider_cards(settings: Mapping[str, Any]) -> tuple[dict[str, An
305
305
 
306
306
  ids = {str(card.get("id")) for card in cards}
307
307
  ordered = _ordered_cards(cards)
308
- first_enabled = next((card for card in ordered if card.get("enabled", True)), ordered[0])
309
- if active_id != str(first_enabled["id"]):
310
- result[LLM_ACTIVE_CARD_KEY] = str(first_enabled["id"])
308
+ first_enabled = next(
309
+ (card for card in ordered if card.get("enabled", True) and not card.get("telegram_llm", False)),
310
+ None,
311
+ )
312
+ desired_active_id = str(first_enabled["id"]) if first_enabled else ""
313
+ if active_id != desired_active_id:
314
+ result[LLM_ACTIVE_CARD_KEY] = desired_active_id
311
315
  changed = True
312
316
  if not any(card.get("telegram_llm") for card in cards):
313
317
  if result.get(LLM_TELEGRAM_CARD_KEY):
@@ -338,7 +342,7 @@ def active_llm_card(settings: Mapping[str, Any]) -> dict[str, Any]:
338
342
  cards = migrated.get(LLM_CARDS_KEY, [])
339
343
  ordered = _ordered_cards(cards) if isinstance(cards, list) else []
340
344
  for card in ordered:
341
- if card.get("enabled", True):
345
+ if card.get("enabled", True) and not card.get("telegram_llm", False):
342
346
  return dict(card)
343
347
  return new_llm_card(DEFAULT_LLM_PROVIDER, card_id=DEFAULT_LLM_CARD_ID)
344
348
 
@@ -370,22 +374,24 @@ def apply_llm_cards_to_settings(
370
374
  old_priority = int(card.get("priority", DEFAULT_LLM_PRIORITY))
371
375
  card["priority"] = 1 if str(card.get("id")) == str(active_card_id) else max(2, old_priority)
372
376
  ordered = _ordered_cards(normalized)
373
- active = next((card for card in ordered if card.get("enabled", True)), ordered[0])
377
+ normal_enabled = [card for card in ordered if card.get("enabled", True) and not card.get("telegram_llm", False)]
378
+ active = normal_enabled[0] if normal_enabled else None
374
379
  result[LLM_CARDS_KEY] = normalized
375
- result[LLM_ACTIVE_CARD_KEY] = active["id"]
380
+ result[LLM_ACTIVE_CARD_KEY] = active["id"] if active else ""
376
381
  result[LLM_TELEGRAM_CARD_KEY] = telegram["id"] if telegram else ""
377
- result["llm_provider"] = active["provider"]
378
-
379
- prefix = _legacy_prefix(str(active["provider"]))
380
- result[f"{prefix}_api_key"] = str(active.get("api_key") or "").strip()
381
- result[f"{prefix}_model_name"] = str(active.get("model") or "").strip()
382
- result[f"{prefix}_base_url"] = str(active.get("base_url") or "").strip()
383
- definition = provider_definition(active["provider"])
384
- for field in definition.extra_fields:
385
- result[f"{prefix}_{field}"] = str(active.get(field) or "").strip()
386
- if active.get("provider") == "cloudflare":
387
- result["cloudflare_account_id"] = str(active.get("account_id") or "").strip()
388
- result["cloudflare_gateway_id"] = str(active.get("gateway_id") or "").strip()
382
+ result["llm_provider"] = active["provider"] if active else DEFAULT_LLM_PROVIDER
383
+
384
+ if active:
385
+ prefix = _legacy_prefix(str(active["provider"]))
386
+ result[f"{prefix}_api_key"] = str(active.get("api_key") or "").strip()
387
+ result[f"{prefix}_model_name"] = str(active.get("model") or "").strip()
388
+ result[f"{prefix}_base_url"] = str(active.get("base_url") or "").strip()
389
+ definition = provider_definition(active["provider"])
390
+ for field in definition.extra_fields:
391
+ result[f"{prefix}_{field}"] = str(active.get(field) or "").strip()
392
+ if active.get("provider") == "cloudflare":
393
+ result["cloudflare_account_id"] = str(active.get("account_id") or "").strip()
394
+ result["cloudflare_gateway_id"] = str(active.get("gateway_id") or "").strip()
389
395
  return result
390
396
 
391
397
 
@@ -281,7 +281,11 @@ def enabled_cards(settings: Mapping[str, Any], pool: str) -> list[dict[str, Any]
281
281
  migrated, _ = ensure_llm_provider_cards(settings)
282
282
  raw_cards = migrated.get("llm_provider_cards", [])
283
283
  cards = [normalize_llm_card(item, index) for index, item in enumerate(raw_cards)] if isinstance(raw_cards, list) else []
284
- enabled = [(index, card) for index, card in enumerate(cards) if bool(card.get("enabled", True))]
284
+ enabled = [
285
+ (index, card)
286
+ for index, card in enumerate(cards)
287
+ if bool(card.get("enabled", True)) and not bool(card.get("telegram_llm", False))
288
+ ]
285
289
  enabled.sort(key=lambda pair: (int(pair[1].get("priority", 1)), pair[0]))
286
290
  return [card for _index, card in enabled]
287
291
  raw_cards = settings.get("media_provider_cards", [])
@@ -369,6 +373,7 @@ def route_json_request(
369
373
  raise ProviderRoutingError(f"Pool de providers inválido: {pool}")
370
374
  candidates = [dict(item) for item in (cards if cards is not None else enabled_cards(settings, pool))]
371
375
  if pool == POOL_LLM:
376
+ candidates = [card for card in candidates if not bool(card.get("telegram_llm", False))]
372
377
  candidates = [
373
378
  card
374
379
  for _index, card in sorted(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
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",