@danhachuel/thunderbolt 0.2.33 → 0.2.35

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.33
5
+ > **Versão deste manual:** 0.2.35
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.33 install
103
+ npx.cmd --yes @danhachuel/thunderbolt@0.2.35 install
104
104
  ```
105
105
 
106
106
  Linux/macOS:
107
107
 
108
108
  ```bash
109
- npx --yes @danhachuel/thunderbolt@0.2.33 install
109
+ npx --yes @danhachuel/thunderbolt@0.2.35 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.
@@ -345,7 +345,7 @@ Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
345
345
 
346
346
  ## 8. Configuração inicial da UI
347
347
 
348
- Na primeira execução, a barra lateral apresenta **Início**, **Pipeline**, **Automação**, **Niche Finder** e **Configurações**. Abra **Configurações > Configurações Técnicas** e reveja. O menu **Pipeline** contém **Criação de Vídeos**, **Criação de Músicas**, **Upload** e **Limpador de Metadados**. O menu **Configurações** contém **Canais**, **Blueprints**, **MCP** e **Configurações Técnicas**. Para YouTube, preencha primeiro o par **OAuth Client ID + OAuth Client Secret** se pretende autorizar uploads. A **YouTube Data API Key** é uma credencial Google Cloud diferente e fica numa área opcional, apenas para o método oficial de métricas; Client ID + Client Secret não formam uma API Key.
348
+ Na primeira execução, a barra lateral apresenta **Início**, **Pipeline**, **Automação**, **Niche Finder** e **Configurações**. O menu expansível **Niche Finder** contém **Niche Finder Kaggle** e **Niche Finder Apify**. A aba **Niche Finder Kaggle** mantém a análise actual; **Niche Finder Apify** é uma área vazia reservada para desenvolvimento futuro. Abra **Configurações > Configurações Técnicas** e reveja. O menu **Pipeline** contém **Criação de Vídeos**, **Criação de Músicas**, **Upload** e **Limpador de Metadados**. O menu **Configurações** contém **Canais**, **Blueprints**, **MCP** e **Configurações Técnicas**. Para YouTube, preencha primeiro o par **OAuth Client ID + OAuth Client Secret** se pretende autorizar uploads. A **YouTube Data API Key** é uma credencial Google Cloud diferente e fica numa área opcional, apenas para o método oficial de métricas; Client ID + Client Secret não formam uma API Key.
349
349
 
350
350
  | Configuração | Finalidade |
351
351
  |---|---|
@@ -371,7 +371,7 @@ Após iniciar a aplicação, valide o seguinte percurso:
371
371
  2. **Blueprints:** coloque um JSON em `~/.thunderbolt/storage/blueprints/importados/` (Windows: `%LOCALAPPDATA%\\THUNDERBOLT\storage\blueprints\importados\`) ou use o carregador da interface.
372
372
  3. **Brandings:** abra a subaba **Brandings** e confirme a listagem dos ficheiros JSON.
373
373
  4. **Canais:** em **Importar do YouTube**, use o método **Página pública — sem API Key** com um URL `/channel/UC...`, um handle ou uma subpágina `/videos`; o parser resolve o ID, consulta a página pública e tenta o RSS quando necessário. Confirme que o resultado abre o formulário de revisão sem Data API Key. Se o canal não existir ou não fornecer metadados, confirme a mensagem clara e que o formulário de uma pesquisa anterior desaparece. A Data API é opcional e fica separada; em **Cadastro manual**, preencha os dados sem qualquer consulta externa.
374
- 5. **Niche Finder:** abra a página na sidebar e aguarde a preparação automática. Confirme que não aparece upload de CSV, botão de download de dataset ou selector de ficheiros. A análise inicial deve carregar directamente métricas, clusters, gráfico Plotly, regras de associação e dados analisados. Altere país, engagement, datas e tags e confirme que **Analisar Nichos** recalcula os resultados.
374
+ 5. **Niche Finder Kaggle:** abra o menu expansível **Niche Finder**, seleccione **Niche Finder Kaggle**, defina os parâmetros dentro do conteúdo principal e confirme que não preparação automática. Antes do clique, não deve existir download de dataset nem análise. Clique em **Analisar Nichos** para iniciar a preparação dos dados e a análise; depois altere país, engagement, datas e tags e clique novamente para aplicar os novos parâmetros. Abra também **Niche Finder Apify** e confirme que a página está vazia, informativa e sem operações.
375
375
  6. **Pipeline > Criação de Vídeos:** teste primeiro o modo **Canal específico** e depois os modos de lote.
376
376
  7. **Criação de Vídeos > Vídeos:** verifique o estado `to_do` e os botões **Iniciar** e **Parar** dentro da subaba. Confirme também que **Criação de Músicas** mostra o mesmo fluxo com título próprio.
377
377
  8. **Upload:** configure o OAuth Client ID e Secret, autorize primeiro o **youtube-automation-agent** na própria aba, confirme o estado **pronto para publicar**, preencha título/descrição/tags e publique um MP4 real. O botão **Autorizar fallback OAuth** existe apenas para redundância; a Data API Key, se configurada, é exclusivamente para consultas oficiais públicas e nunca substitui OAuth.
@@ -387,13 +387,13 @@ Após iniciar a aplicação, valide o seguinte percurso:
387
387
 
388
388
  ## Niche Finder
389
389
 
390
- A página **Niche Finder** integra a lógica adaptada do projecto open source [johanfortus/Niche-Finder](https://github.com/johanfortus/Niche-Finder), cujo projecto original usa K-Means e FP-Growth sobre o dataset público [Trending Youtube Video Statistics (113 Countries)](https://www.kaggle.com/datasets/asaniczka/trending-youtube-videos-113-countries). No Thunderbolt não existe Flask, rota HTTP adicional, template HTML, JavaScript D3 ou segundo processo; toda a análise é síncrona no Streamlit.
390
+ A página **Niche Finder Kaggle** integra a lógica adaptada do projecto open source [johanfortus/Niche-Finder](https://github.com/johanfortus/Niche-Finder), cujo projecto original usa K-Means e FP-Growth sobre o dataset público [Trending Youtube Video Statistics (113 Countries)](https://www.kaggle.com/datasets/asaniczka/trending-youtube-videos-113-countries). No Thunderbolt não existe Flask, rota HTTP adicional, template HTML, JavaScript D3 ou segundo processo; toda a análise é síncrona no Streamlit.
391
391
 
392
- Ao abrir a página, o Thunderbolt prepara automaticamente os dados públicos através do KaggleHub e inicia a análise. O utilizador não precisa de baixar datasets, carregar CSVs, preparar planilhas ou escolher ficheiros. A interface apresenta directamente os parâmetros da busca e os resultados.
392
+ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega o dataset e não inicia a análise. A instalação das dependências continua automática, mas a operação é manual: os parâmetros ficam dentro da própria aba e o utilizador deve clicar em **Analisar Nichos**. depois desse clique o KaggleHub prepara ou reutiliza a cache local; não upload de CSV, botão de download manual ou selector de ficheiros.
393
393
 
394
- 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. 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. A análise inicial é automática; o botão **Analisar Nichos** recalcula os resultados depois de alterar os 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.
394
+ 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.
395
395
 
396
- 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 @danhachuel/thunderbolt@0.2.33 install`; o instalador detecta e reutiliza o que já estiver válido.
396
+ 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 @danhachuel/thunderbolt@0.2.35 install`; o instalador detecta e reutiliza o que já estiver válido.
397
397
 
398
398
  ## Pipeline: Criação de Vídeos, Criação de Músicas e Automação
399
399
 
package/README.md CHANGED
@@ -17,7 +17,7 @@ A primeira versão implementa a camada UI independente com:
17
17
  | Canais | Subabas de importação pública sem API Key, Data API opcional e cadastro manual independente |
18
18
  | Criação de Vídeos / Criação de Músicas | 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; a segunda página reutiliza o mesmo fluxo com título próprio |
19
19
  | Automação | Lista de vídeos e canais, selectores editáveis de Blueprint/voz padrão, Automação ON, horário diário HH:MM e worker local baseado no relógio do computador |
20
- | Niche Finder | Preparação e análise automáticas, filtros, K-Means, FP-Growth, regras de associação, clusters e gráfico Plotly nativo |
20
+ | Niche Finder | Menu expansível com Niche Finder Kaggle (análise manual, K-Means e FP-Growth) e Niche Finder Apify reservado para desenvolvimento futuro |
21
21
  | Upload | YouTube via `youtube-automation-agent` adaptado internamente, OAuth directo de redundância, Upload directo experimental, TikTok, Instagram e Facebook Pages no front end |
22
22
  | MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
23
23
  | Limpador de Metadados | Upload isolado de vídeos terceiros, limpeza FFmpeg, edição de título/descrição/tags e manifesto JSON |
@@ -26,9 +26,9 @@ A primeira versão implementa a camada UI independente com:
26
26
 
27
27
  Os adaptadores do MoneyPrinterTurbo e de publicação nas plataformas são ligados pelas configurações locais e pelos pontos de integração em `integrations/`. A UI não inventa dados quando um serviço externo ou credencial não está disponível.
28
28
 
29
- ## Navegação da UI 0.2.33
29
+ ## Navegação da UI 0.2.35
30
30
 
31
- A barra lateral mantém apenas os cinco níveis principais, nesta ordem: **Início**, **Pipeline**, **Automação**, **Niche Finder** e **Configurações**. **Pipeline** é expansível e contém **Criação de Vídeos**, **Criação de Músicas**, **Upload** e **Limpador de Metadados**. **Configurações** é expansível e contém **Canais**, **Blueprints**, **MCP** e **Configurações Técnicas**. O Início reúne o dashboard e as filas do Pipeline, sem botões de acções rápidas.
31
+ A barra lateral mantém os níveis principais, nesta ordem: **Início**, **Pipeline**, **Automação**, **Niche Finder** e **Configurações**. **Pipeline** é expansível e contém **Criação de Vídeos**, **Criação de Músicas**, **Upload** e **Limpador de Metadados**. **Niche Finder** é expansível e contém **Niche Finder Kaggle** e **Niche Finder Apify**. **Configurações** é expansível e contém **Canais**, **Blueprints**, **MCP** e **Configurações Técnicas**. O Início reúne o dashboard e as filas do Pipeline, sem botões de acções rápidas.
32
32
 
33
33
  ## Instalação
34
34
 
@@ -115,9 +115,9 @@ thunderbolt
115
115
  No Windows PowerShell, se `npx` for bloqueado por `npx.ps1`, use directamente `npx.cmd`:
116
116
 
117
117
  ```powershell
118
- npx.cmd --yes @danhachuel/thunderbolt@0.2.33 install
119
- npx.cmd --yes @danhachuel/thunderbolt@0.2.33 doctor
120
- npx.cmd --yes @danhachuel/thunderbolt@0.2.33
118
+ npx.cmd --yes @danhachuel/thunderbolt@0.2.35 install
119
+ npx.cmd --yes @danhachuel/thunderbolt@0.2.35 doctor
120
+ npx.cmd --yes @danhachuel/thunderbolt@0.2.35
121
121
  ```
122
122
 
123
123
  Como alternativa, pode permitir scripts para o seu utilizador:
@@ -137,11 +137,11 @@ Após `install`, o launcher usa o ambiente virtual instalado em `~/.thunderbolt/
137
137
 
138
138
  ## Niche Finder
139
139
 
140
- A página **Niche Finder** integra a lógica do projecto open source [johanfortus/Niche-Finder](https://github.com/johanfortus/Niche-Finder) directamente no processo Streamlit. O Thunderbolt não inicia Flask, não abre uma porta adicional e não copia templates HTML ou D3. Ao abrir a página, o próprio Thunderbolt prepara automaticamente os dados públicos necessários através do KaggleHub, guarda a cache localmente e executa a análise; o utilizador não precisa de baixar datasets, carregar CSVs ou preparar planilhas.
140
+ O menu expansível **Niche Finder** contém duas páginas. **Niche Finder Kaggle** é a página actual de análise e integra a lógica do projecto open source [johanfortus/Niche-Finder](https://github.com/johanfortus/Niche-Finder) directamente no processo Streamlit. O Thunderbolt não inicia Flask, não abre uma porta adicional e não copia templates HTML ou D3. Ao abrir a página, o Thunderbolt não descarrega dados, não prepara o dataset e não executa análises. A instalação das dependências continua automática, mas a operação é manual: o utilizador define os parâmetros dentro da própria aba e clica em **Analisar Nichos**. Só nesse momento o KaggleHub prepara ou reutiliza a cache local do dataset e a análise é executada.
141
141
 
142
- A interface apresenta directamente os parâmetros da busca: número de clusters entre 2 e 10, suporte mínimo entre 0,01 e 0,50, país, categoria de engagement, intervalo de datas e tags. A análise inicial é executada automaticamente e **Analisar Nichos** permite recalcular os resultados depois de alterar os filtros. O núcleo aplica normalização, filtros, `log1p`, `StandardScaler`, K-Means e FP-Growth. Os resultados aparecem em DataFrames para clusters, itemsets frequentes, regras de associação e dados analisados, acompanhados por uma visualização Plotly nativa e pesquisa de palavras nos clusters.
142
+ A interface apresenta dentro da aba os parâmetros da busca: número de clusters entre 2 e 10, suporte mínimo entre 0,01 e 0,50, país, categoria de engagement, intervalo de datas e tags. A página permanece sem resultados até o primeiro clique em **Analisar Nichos**; depois, se os parâmetros forem alterados, mostra os resultados anteriores e pede novo clique para aplicar os filtros actuais. O núcleo aplica normalização, filtros, `log1p`, `StandardScaler`, K-Means e FP-Growth. Os resultados aparecem em DataFrames para clusters, itemsets frequentes, regras de associação e dados analisados, acompanhados por uma visualização Plotly nativa e pesquisa de palavras nos clusters.
143
143
 
144
- As dependências adicionais são instaladas pelo fluxo normal do pacote: `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub`. Em instalações existentes, execute novamente `npx.cmd --yes @danhachuel/thunderbolt@0.2.31 install`; o instalador detecta e reutiliza componentes já válidos.
144
+ **Niche Finder Apify** é uma página vazia reservada para a futura integração com o Apify e não executa nenhuma operação. As dependências adicionais são instaladas pelo fluxo normal do pacote: `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub`. Em instalações existentes, execute novamente `npx.cmd --yes @danhachuel/thunderbolt@0.2.35 install`; o instalador detecta e reutiliza componentes já válidos.
145
145
 
146
146
  ## Armazenamento local
147
147
 
package/app/main.py CHANGED
@@ -21,7 +21,7 @@ from hermes_ui.domain import STAGES, create_batch, create_channel, create_tasks_
21
21
  from hermes_ui.automation_worker import load_worker_status
22
22
  from hermes_ui.storage import BLUEPRINTS, ensure_storage, list_blueprint_files, load_blueprint_file, now, read_json, write_json
23
23
  from app.modules.niche_finder.core import NicheAnalysisError, run_niche_analysis
24
- from app.modules.niche_finder.data_loader import DatasetError, download_kaggle_dataset, load_dataframe
24
+ from app.modules.niche_finder.data_loader import DatasetError, download_kaggle_dataset
25
25
  from hermes_ui.blueprints import create_blueprint_from_link, list_branding_files, save_generated_blueprint
26
26
  from hermes_ui.metadata_cleaner import build_description, clean_video_metadata, list_edit_records, metadata_manifest, normalize_tags, save_edit_record, store_external_video
27
27
  from hermes_ui.mcp import detect_local_service, install_skill_locally, load_integrations, load_server_config, read_packaged_skill, save_server_config, update_integration
@@ -630,79 +630,56 @@ def render_music_creation():
630
630
  render_new_video(page_title="Criação de Músicas")
631
631
 
632
632
 
633
- @st.cache_data(show_spinner=False)
634
- def _cached_niche_dataset(path_str: str, modified_ns: int):
635
- return load_dataframe(path_str)
636
-
637
-
638
633
  @st.cache_data(show_spinner=False)
639
634
  def _cached_niche_download():
635
+ """Download or reuse the dataset only after the user submits the form."""
640
636
  return str(download_kaggle_dataset())
641
637
 
642
638
 
643
- def _load_automatic_niche_dataset():
644
- dataset_path = _cached_niche_download()
645
- path = Path(dataset_path)
646
- return dataset_path, _cached_niche_dataset(dataset_path, path.stat().st_mtime_ns)
647
-
648
-
649
- def _niche_tag_options(frame) -> list[str]:
650
- tags: set[str] = set()
651
- for value in frame.get("video_tags", []):
652
- text = str(value or "").replace('"', "").replace("'", "")
653
- for tag in re.split(r"[|,]", text.strip("[]")):
654
- tag = re.sub(r"\s+", " ", tag).strip().lower()
655
- if tag and tag not in {"nan", "none", "null"}:
656
- tags.add(tag)
657
- return sorted(tags)
658
-
659
-
660
- def _niche_date_bounds(frame):
661
- dates = frame["publish_date"].dropna().astype(str)
662
- if dates.empty:
663
- return None, None
664
- return dates.min(), dates.max()
665
-
666
-
667
639
  def render_niche_finder():
668
- st.title("Niche Finder")
669
- st.caption("Busca automática de padrões, nichos e tags para orientar canais faceless.")
670
- st.info("O Thunderbolt prepara e analisa automaticamente os dados necessários. Não é necessária configuração manual.")
671
-
672
- try:
673
- with st.spinner("A preparar automaticamente o Niche Finder…"):
674
- dataset_path, frame = _load_automatic_niche_dataset()
675
- except (DatasetError, OSError) as exc:
676
- st.error("Não foi possível preparar automaticamente o Niche Finder neste momento. Verifique a ligação à Internet e tente novamente.")
677
- with st.expander("Detalhes técnicos"):
678
- st.caption(str(exc))
679
- return
680
-
681
- signature = f"{dataset_path}:{Path(dataset_path).stat().st_mtime_ns}"
682
- if st.session_state.get("niche_results_signature") != signature:
683
- st.session_state.pop("niche_results", None)
684
- st.session_state["niche_results_signature"] = signature
685
-
686
- min_date, max_date = _niche_date_bounds(frame)
687
- countries = ["Todos"] + sorted(str(value) for value in frame["country"].dropna().unique())
688
- tag_options = _niche_tag_options(frame)
689
- with st.sidebar:
640
+ st.title("Niche Finder Kaggle")
641
+ st.caption("Busca de padrões, nichos e tags para orientar canais faceless.")
642
+ st.info("A instalação das dependências é automática. A operação não é: defina os parâmetros abaixo e clique em **Analisar Nichos** para iniciar.")
643
+
644
+ default_start = date(2023, 9, 20)
645
+ default_end = date.today()
646
+ country_options = [
647
+ "Todos", "US", "BR", "GB", "CA", "AU", "DE", "FR", "ES", "IT", "JP", "KR", "IN", "MX", "AR", "PT",
648
+ ]
649
+ with st.container(border=True):
690
650
  st.subheader("Parâmetros da busca")
691
- n_clusters = st.slider("Número de Clusters", 2, 10, 5, key="niche_n_clusters")
692
- min_support = st.slider("Suporte Mínimo", 0.01, 0.5, 0.05, 0.01, format="%.2f", key="niche_min_support")
693
- country = st.selectbox("País", countries, key="niche_country")
694
- engagement = st.selectbox("Engagement", ["Todos", "High", "Moderate", "Low"], key="niche_engagement")
695
- start_date = st.date_input("Data inicial", value=date.fromisoformat(min_date), min_value=date.fromisoformat(min_date), max_value=date.fromisoformat(max_date), key="niche_start_date") if min_date else None
696
- end_date = st.date_input("Data final", value=date.fromisoformat(max_date), min_value=date.fromisoformat(min_date), max_value=date.fromisoformat(max_date), key="niche_end_date") if max_date else None
697
- selected_tags = st.multiselect("Tags opcionais", tag_options, key="niche_tags")
698
- analyse = st.button("Analisar Nichos", type="primary", use_container_width=True, key="niche_analyse")
699
-
651
+ st.caption("Estes controlos pertencem a esta aba. Nenhum dataset é descarregado e nenhuma análise é executada enquanto não clicar no botão.")
652
+ with st.form("niche_finder_parameters", clear_on_submit=False):
653
+ parameter_cols = st.columns(3)
654
+ with parameter_cols[0]:
655
+ n_clusters = st.slider("Número de Clusters", 2, 10, 5, key="niche_n_clusters")
656
+ min_support = st.slider("Suporte Mínimo", 0.01, 0.5, 0.05, 0.01, format="%.2f", key="niche_min_support")
657
+ with parameter_cols[1]:
658
+ country = st.selectbox("País", country_options, key="niche_country")
659
+ engagement = st.selectbox("Engagement", ["Todos", "High", "Moderate", "Low"], key="niche_engagement")
660
+ with parameter_cols[2]:
661
+ start_date = st.date_input("Data inicial", value=default_start, min_value=date(2020, 1, 1), max_value=default_end, key="niche_start_date")
662
+ end_date = st.date_input("Data final", value=default_end, min_value=date(2020, 1, 1), max_value=default_end, key="niche_end_date")
663
+ tags_text = st.text_input("Tags opcionais", key="niche_tags_text", placeholder="Ex.: history, facts, documentary")
664
+ analyse = st.form_submit_button("Analisar Nichos", type="primary", use_container_width=True)
665
+
666
+ current_parameters = {
667
+ "n_clusters": n_clusters,
668
+ "min_support": min_support,
669
+ "country": country,
670
+ "engagement": engagement,
671
+ "start_date": start_date.isoformat() if start_date else None,
672
+ "end_date": end_date.isoformat() if end_date else None,
673
+ "tags": [tag.strip() for tag in re.split(r"[,|]", tags_text) if tag.strip()],
674
+ }
700
675
  results = st.session_state.get("niche_results")
701
- if results is None or analyse:
676
+ if analyse:
702
677
  try:
703
678
  if start_date and end_date and start_date > end_date:
704
679
  raise NicheAnalysisError("A data inicial não pode ser posterior à data final.")
705
- with st.spinner("A analisar nichos automaticamente…"):
680
+ with st.spinner("A preparar os dados apenas porque solicitou a análise…"):
681
+ dataset_path = _cached_niche_download()
682
+ with st.spinner("A executar a análise dos nichos…"):
706
683
  results = run_niche_analysis(
707
684
  str(dataset_path),
708
685
  n_clusters=n_clusters,
@@ -711,15 +688,23 @@ def render_niche_finder():
711
688
  end_date=end_date.isoformat() if end_date else None,
712
689
  country=country,
713
690
  engagement=engagement,
714
- tags=selected_tags,
691
+ tags=current_parameters["tags"],
715
692
  )
716
693
  st.session_state["niche_results"] = results
694
+ st.session_state["niche_last_parameters"] = current_parameters
695
+ st.success("Análise concluída.")
717
696
  except (NicheAnalysisError, DatasetError, OSError) as exc:
718
- st.error("Não foi possível concluir a análise automática com os filtros actuais.")
697
+ st.error("Não foi possível concluir a análise solicitada com os parâmetros actuais.")
719
698
  with st.expander("Detalhes técnicos"):
720
699
  st.caption(str(exc))
721
700
  return
722
701
 
702
+ if results is None:
703
+ st.caption("Ainda não existe uma análise nesta sessão. Ajuste os parâmetros e clique em **Analisar Nichos**.")
704
+ return
705
+ if st.session_state.get("niche_last_parameters") != current_parameters:
706
+ st.warning("Os resultados apresentados pertencem à última análise executada. Clique em **Analisar Nichos** para aplicar os parâmetros actuais.")
707
+
723
708
  summary = results.get("summary", {})
724
709
  metric_cols = st.columns(4)
725
710
  for col, (label, value) in zip(metric_cols, [("Registos analisados", summary.get("rows_filtered", 0)), ("Clusters", summary.get("cluster_count", 0)), ("Itemsets frequentes", summary.get("frequent_item_count", 0)), ("Regras de associação", summary.get("association_rule_count", 0))]):
@@ -758,6 +743,12 @@ def render_niche_finder():
758
743
  st.dataframe(results["raw_data"], use_container_width=True, hide_index=True)
759
744
 
760
745
 
746
+ def render_niche_finder_apify():
747
+ st.title("Niche Finder Apify")
748
+ st.caption("Área reservada para a futura integração do Niche Finder com o Apify.")
749
+ st.info("Esta aba está preparada para desenvolvimento futuro e ainda não executa nenhuma operação.")
750
+
751
+
761
752
  def render_videos():
762
753
  st.subheader("Vídeos e backlog")
763
754
  st.caption("Acompanhamento dos vídeos criados, estados da pipeline e controlos de execução.")
@@ -1513,6 +1504,10 @@ def main():
1513
1504
  ("MCP", ":material/hub:", "MCP"),
1514
1505
  ("Configurações Técnicas", ":material/settings:", "Configurações Técnicas"),
1515
1506
  ]
1507
+ niche_finder_items = [
1508
+ ("Niche Finder Kaggle", ":material/search:", "Niche Finder Kaggle"),
1509
+ ("Niche Finder Apify", ":material/api:", "Niche Finder Apify"),
1510
+ ]
1516
1511
  top_pages = [
1517
1512
  ("Início", ":material/home:", "Início"),
1518
1513
  ("Pipeline", ":material/account_tree:", "Pipeline"),
@@ -1525,10 +1520,11 @@ def main():
1525
1520
  "Novo vídeo": "Criação de Vídeos",
1526
1521
  "Vídeos": "Criação de Vídeos",
1527
1522
  "Limpador de metadado": "Limpador de Metadados",
1523
+ "Niche Finder": "Niche Finder Kaggle",
1528
1524
  "Configurações Técnicas": "Configurações Técnicas",
1529
1525
  }
1530
1526
  current_page = aliases.get(st.session_state.get("page", "Início"), st.session_state.get("page", "Início"))
1531
- if current_page not in {item[0] for item in top_pages + pipeline_items + settings_items}:
1527
+ if current_page not in {item[0] for item in top_pages + pipeline_items + niche_finder_items + settings_items}:
1532
1528
  current_page = "Início"
1533
1529
  st.session_state["page"] = current_page
1534
1530
 
@@ -1548,6 +1544,10 @@ def main():
1548
1544
  with st.expander("Pipeline", expanded=current_page in {item[0] for item in pipeline_items}, icon=":material/account_tree:"):
1549
1545
  for child_target, child_icon, child_label in pipeline_items:
1550
1546
  render_nav_button(child_target, child_icon, child_label, child=True)
1547
+ elif target == "Niche Finder":
1548
+ with st.expander("Niche Finder", expanded=current_page in {item[0] for item in niche_finder_items}, icon=":material/search:"):
1549
+ for child_target, child_icon, child_label in niche_finder_items:
1550
+ render_nav_button(child_target, child_icon, child_label, child=True)
1551
1551
  elif target == "Configurações":
1552
1552
  with st.expander("Configurações", expanded=current_page in {item[0] for item in settings_items}, icon=":material/settings:"):
1553
1553
  for child_target, child_icon, child_label in settings_items:
@@ -1560,7 +1560,8 @@ def main():
1560
1560
  "Criação de Vídeos": render_new_video,
1561
1561
  "Criação de Músicas": render_music_creation,
1562
1562
  "Automação": render_automation,
1563
- "Niche Finder": render_niche_finder,
1563
+ "Niche Finder Kaggle": render_niche_finder,
1564
+ "Niche Finder Apify": render_niche_finder_apify,
1564
1565
  "Upload": render_upload,
1565
1566
  "Limpador de Metadados": render_metadata_cleaner,
1566
1567
  "Canais": render_channels,
@@ -103,7 +103,7 @@ def _download_with_kagglehub() -> Path | None:
103
103
 
104
104
 
105
105
  def download_kaggle_dataset() -> Path:
106
- """Prepare and cache the public Niche-Finder dataset without user interaction."""
106
+ """Prepare and cache the public Niche-Finder dataset after an explicit user action."""
107
107
  ensure_data_dir()
108
108
  if _has_usable_cached_dataset():
109
109
  return DEFAULT_DATASET_PATH
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.2.33",
3
+ "version": "0.2.35",
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",