@danhachuel/thunderbolt 0.3.45 → 0.3.46

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.45
5
+ > **Versão deste manual:** 0.3.46
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.45 install
103
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.46 install
104
104
  ```
105
105
 
106
106
  Linux/macOS:
107
107
 
108
108
  ```bash
109
- npx --yes --prefer-online @danhachuel/thunderbolt@0.3.45 install
109
+ npx --yes --prefer-online @danhachuel/thunderbolt@0.3.46 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.45 install --purge-data
117
+ npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.46 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.
@@ -363,11 +363,11 @@ Em **Configuração API > API Keys**, o expander **LLM — providers e modelos**
363
363
 
364
364
  O bloco **Limite LLM NVIDIA NIM** 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
 
366
- O expander **Imagem e Video** substitui o antigo bloco isolado da Nano Banana. Os cartões disponíveis são **Nano Banana**, **Pollinations.ai**, **Agnes AI**, **Hugging Face Inference API**, **Cloudflare Workers AI**, **InferencePort Proxy**, **阿里云 (Alibaba Cloud Model Studio)**, **KIE AI** e **FAL AI**. Em cada cartão, configure a API key/token, o modelo, a Base URL, o estado **Provider activo**, a participação no **Pool Imagem**, a participação no **Pool Vídeo** e a prioridade. O botão **Testar Chamada API** faz apenas uma verificação read-only; não inicia geração de imagem ou vídeo.
366
+ O expander **Imagem e Video** substitui o antigo bloco isolado da Nano Banana. Os cartões disponíveis são **Nano Banana**, **Pollinations.ai**, **Agnes AI**, **Hugging Face Inference API**, **Cloudflare Workers AI**, **InferencePort Proxy**, **阿里云 (Alibaba Cloud Model Studio)**, **KIE AI** e **FAL AI**. Em cada cartão, configure a API key/token, o modelo, a Base URL, o estado **Provider activo**, a participação no **Pool Imagem**, a participação no **Pool Vídeo** e a prioridade. **Image Size** e **Aspect Ratio** não aparecem nos cartões e não são editáveis: são defaults internos aplicados automaticamente no prompt da geração. O botão **Testar Chamada API** faz apenas uma verificação read-only; não inicia geração de imagem ou vídeo.
367
367
 
368
368
  | Provider | Configuração mínima | Pool suportado |
369
369
  |---|---|---|
370
- | Nano Banana | API key, modelo Gemini e parâmetros da imagem | Imagem |
370
+ | Nano Banana | API key e modelo Gemini; composição aplicada internamente | Imagem |
371
371
  | Pollinations.ai | API key/token, modelo e Base URL | Imagem e vídeo |
372
372
  | Agnes AI | API key, modelo e Base URL | Imagem e vídeo |
373
373
  | Hugging Face Inference API | Token, modelo e `https://router.huggingface.co/v1` | Imagem |
@@ -522,7 +522,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
522
522
 
523
523
  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.
524
524
 
525
- 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.45 install`; o instalador detecta e reutiliza o que já estiver válido.
525
+ 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.46 install`; o instalador detecta e reutiliza o que já estiver válido.
526
526
 
527
527
  ### Niche Finder Apify
528
528
 
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.45
72
+ ## Navegação da UI 0.3.46
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
 
@@ -85,7 +85,7 @@ Em **Configuração API > API Keys**, a subaba **Fontes de materiais** segue o p
85
85
 
86
86
  A subaba **Serviços e modelos** mantém as credenciais de providers e serviços que não são fontes de materiais. A chave `gemini_image_api_key` da Nano Banana continua separada de `gemini_api_key` do LLM textual. `INNERTUBE_API_KEY` permanece exclusivamente em **Configurações > Contas Google**, no bloco de configuração global fora dos cartões, e não é duplicada em API Keys.
87
87
 
88
- 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**. 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.
88
+ 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.
89
89
 
90
90
  O router mantém três pools independentes: **LLM textual**, **imagem** e **vídeo**. O failover só passa ao cartão seguinte em quota, timeout, erro de transporte ou HTTP transitório; erros de credencial, endpoint/modelo inexistente ou payload inválido ficam registados e não são mascarados por um provider diferente. O pool de vídeo externo é opcional: quando desligado, o worker continua a usar o motor local MoneyPrinterTurbo; quando ligado, usa apenas os cartões marcados para Pool Vídeo.
91
91
 
@@ -16,7 +16,14 @@ from urllib.parse import urljoin
16
16
 
17
17
  import requests
18
18
 
19
- from .media_providers import media_cards_for_pool, media_provider_definition
19
+ from .media_providers import (
20
+ INTERNAL_IMAGE_ASPECT_RATIO,
21
+ INTERNAL_IMAGE_SIZE,
22
+ INTERNAL_VIDEO_ASPECT_RATIO,
23
+ INTERNAL_VIDEO_SIZE,
24
+ media_cards_for_pool,
25
+ media_provider_definition,
26
+ )
20
27
  from .provider_routing import (
21
28
  POOL_IMAGE,
22
29
  POOL_VIDEO,
@@ -52,6 +59,28 @@ def _headers(card: Mapping[str, Any], *, fal: bool = False) -> dict[str, str]:
52
59
  return headers
53
60
 
54
61
 
62
+ def _append_generation_constraints(
63
+ prompt: str,
64
+ *,
65
+ kind: str,
66
+ aspect_ratio: str = "",
67
+ size: str = "",
68
+ ) -> str:
69
+ """Append controlled rendering constraints without exposing editable UI fields."""
70
+ clean = str(prompt or "").strip()
71
+ if kind == "image":
72
+ ratio = str(aspect_ratio or INTERNAL_IMAGE_ASPECT_RATIO).strip()
73
+ resolution = str(size or INTERNAL_IMAGE_SIZE).strip()
74
+ constraints = f"Target image composition: {ratio} aspect ratio, {resolution} resolution."
75
+ else:
76
+ ratio = str(aspect_ratio or INTERNAL_VIDEO_ASPECT_RATIO).strip()
77
+ resolution = str(size or INTERNAL_VIDEO_SIZE).strip()
78
+ constraints = f"Target video composition: {ratio} aspect ratio, {resolution} resolution."
79
+ if not clean:
80
+ return constraints
81
+ return f"{clean}\n\n{constraints}"
82
+
83
+
55
84
  def _decode_data(value: Any) -> bytes | None:
56
85
  if not isinstance(value, str) or not value.strip():
57
86
  return None
@@ -142,14 +171,20 @@ def _image_request(card: dict[str, Any], prompt: str) -> Any:
142
171
  provider = str(card.get("provider") or "").strip().lower()
143
172
  style = str(card.get("api_style") or media_provider_definition(provider).api_style)
144
173
  endpoint = _image_endpoint(card)
174
+ constrained_prompt = _append_generation_constraints(
175
+ prompt,
176
+ kind="image",
177
+ aspect_ratio=str(card.get("aspect_ratio") or ""),
178
+ size=str(card.get("image_size") or ""),
179
+ )
145
180
  if style == "cloudflare":
146
- return requests.post(endpoint, headers=_headers(card), json={"prompt": prompt}, timeout=180)
181
+ return requests.post(endpoint, headers=_headers(card), json={"prompt": constrained_prompt}, timeout=180)
147
182
  if style == "fal_queue":
148
- return requests.post(endpoint, headers=_headers(card, fal=True), json={"prompt": prompt, "num_images": 1}, timeout=180)
183
+ return requests.post(endpoint, headers=_headers(card, fal=True), json={"prompt": constrained_prompt, "num_images": 1}, timeout=180)
149
184
  if style == "dashscope":
150
- body = {"model": _model(card), "input": {"prompt": prompt}, "parameters": {"size": "1024*1024", "n": 1}}
185
+ body = {"model": _model(card), "input": {"prompt": constrained_prompt}, "parameters": {"n": 1}}
151
186
  return requests.post(endpoint, headers=_headers(card), json=body, timeout=180)
152
- body = {"model": _model(card), "prompt": prompt, "n": 1, "response_format": "b64_json"}
187
+ body = {"model": _model(card), "prompt": constrained_prompt, "n": 1, "response_format": "b64_json"}
153
188
  return requests.post(endpoint, headers=_headers(card), json=body, timeout=180)
154
189
 
155
190
 
@@ -171,10 +206,17 @@ def generate_image_for_card(
171
206
  merged = dict(settings)
172
207
  merged["gemini_image_api_key"] = _api_key(card)
173
208
  merged["gemini_image_model"] = _model(card) or merged.get("gemini_image_model") or "gemini-3.1-flash-image"
209
+ merged["gemini_image_aspect_ratio"] = str(card.get("aspect_ratio") or INTERNAL_IMAGE_ASPECT_RATIO)
210
+ merged["gemini_image_size"] = str(card.get("image_size") or INTERNAL_IMAGE_SIZE)
174
211
  try:
175
212
  return generate_thumbnail_image(
176
213
  merged,
177
- prompt,
214
+ _append_generation_constraints(
215
+ prompt,
216
+ kind="image",
217
+ aspect_ratio=str(card.get("aspect_ratio") or ""),
218
+ size=str(card.get("image_size") or ""),
219
+ ),
178
220
  topic=topic,
179
221
  variant_index=variant_index,
180
222
  lettering_text=lettering_text,
@@ -260,7 +302,15 @@ def _video_endpoint(card: Mapping[str, Any]) -> str:
260
302
  def _video_request(card: dict[str, Any], prompt: str, image_url: str = "") -> Any:
261
303
  style = str(card.get("api_style") or media_provider_definition(card.get("provider")).api_style)
262
304
  endpoint = _video_endpoint(card)
263
- body: dict[str, Any] = {"model": _model(card), "prompt": prompt}
305
+ body: dict[str, Any] = {
306
+ "model": _model(card),
307
+ "prompt": _append_generation_constraints(
308
+ prompt,
309
+ kind="video",
310
+ aspect_ratio=str(card.get("aspect_ratio") or ""),
311
+ size=str(card.get("video_size") or ""),
312
+ ),
313
+ }
264
314
  if image_url:
265
315
  body["image_url"] = image_url
266
316
  if style == "fal_queue":
@@ -12,6 +12,12 @@ MEDIA_VIDEO_ACTIVE_CARD_KEY = "media_video_active_card_id"
12
12
  MEDIA_IMAGE_ACTIVE_PROVIDER_KEY = "media_image_provider"
13
13
  MEDIA_VIDEO_ACTIVE_PROVIDER_KEY = "media_video_provider"
14
14
 
15
+ # Mantidos internamente para preservar compatibilidade e controlar o prompt dos providers.
16
+ INTERNAL_IMAGE_ASPECT_RATIO = "16:9"
17
+ INTERNAL_IMAGE_SIZE = "1K"
18
+ INTERNAL_VIDEO_ASPECT_RATIO = "16:9"
19
+ INTERNAL_VIDEO_SIZE = "1080p"
20
+
15
21
 
16
22
  @dataclass(frozen=True, slots=True)
17
23
  class MediaProviderDefinition:
@@ -35,7 +41,6 @@ MEDIA_PROVIDER_CATALOG: tuple[MediaProviderDefinition, ...] = (
35
41
  default_base_url="https://generativelanguage.googleapis.com/v1beta",
36
42
  supports_image=True,
37
43
  api_style="gemini_interactions",
38
- extra_fields=("aspect_ratio", "image_size"),
39
44
  description="Geração de imagem nativa Gemini para thumbnails e artes.",
40
45
  ),
41
46
  MediaProviderDefinition(
@@ -170,6 +175,9 @@ def normalize_media_card(card: Any, index: int = 0) -> dict[str, Any]:
170
175
  }
171
176
  for field in definition.extra_fields:
172
177
  result[field] = str(source.get(field) or "").strip()
178
+ if provider == "nano_banana":
179
+ result["aspect_ratio"] = str(source.get("aspect_ratio") or INTERNAL_IMAGE_ASPECT_RATIO).strip()
180
+ result["image_size"] = str(source.get("image_size") or INTERNAL_IMAGE_SIZE).strip()
173
181
  test_result = source.get("test_result")
174
182
  if isinstance(test_result, Mapping) and str(test_result.get("status") or "") in {"success", "error"}:
175
183
  result["test_result"] = {
@@ -215,8 +223,8 @@ def ensure_media_provider_cards(settings: Mapping[str, Any]) -> tuple[dict[str,
215
223
  "api_key": legacy_key,
216
224
  "model": legacy_model,
217
225
  "base_url": "https://generativelanguage.googleapis.com/v1beta",
218
- "aspect_ratio": str(result.get("gemini_image_aspect_ratio") or "16:9"),
219
- "image_size": str(result.get("gemini_image_size") or "1K"),
226
+ "aspect_ratio": str(result.get("gemini_image_aspect_ratio") or INTERNAL_IMAGE_ASPECT_RATIO),
227
+ "image_size": str(result.get("gemini_image_size") or INTERNAL_IMAGE_SIZE),
220
228
  "enabled": True,
221
229
  },
222
230
  0,
@@ -267,8 +275,8 @@ def apply_media_provider_cards_to_settings(settings: Mapping[str, Any], cards: l
267
275
  if nano_card:
268
276
  result["gemini_image_api_key"] = str(nano_card.get("api_key") or "")
269
277
  result["gemini_image_model"] = str(nano_card.get("model") or "gemini-3.1-flash-image")
270
- result["gemini_image_aspect_ratio"] = str(nano_card.get("aspect_ratio") or result.get("gemini_image_aspect_ratio") or "16:9")
271
- result["gemini_image_size"] = str(nano_card.get("image_size") or result.get("gemini_image_size") or "1K")
278
+ result["gemini_image_aspect_ratio"] = str(nano_card.get("aspect_ratio") or result.get("gemini_image_aspect_ratio") or INTERNAL_IMAGE_ASPECT_RATIO)
279
+ result["gemini_image_size"] = str(nano_card.get("image_size") or result.get("gemini_image_size") or INTERNAL_IMAGE_SIZE)
272
280
  return result
273
281
 
274
282
 
@@ -17,7 +17,7 @@ from hermes_ui.script_documents import save_script_document
17
17
  from hermes_ui.script_generation import generate_script_document
18
18
  from hermes_ui.storage import STORAGE, ensure_storage, read_json, write_json
19
19
  from hermes_ui.llm_providers import active_llm_card, provider_definition
20
- from hermes_ui.media_generation import MediaGenerationError, generate_image_from_pool, generate_video_from_pool
20
+ from hermes_ui.media_generation import MediaGenerationError, _append_generation_constraints, generate_image_from_pool, generate_video_from_pool
21
21
  from hermes_ui.media_providers import media_cards_for_pool
22
22
  from hermes_ui.thumbnail_generation import ThumbnailGenerationError, generate_thumbnail_image
23
23
 
@@ -554,7 +554,10 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
554
554
  video_cards = media_cards_for_pool(settings, "video")
555
555
  if bool(settings.get("media_video_pool_enabled")) and video_cards:
556
556
  try:
557
- video_prompt = f"Título: {title}\n\nRoteiro:\n{str(script.get('content') or '')[:12000]}"
557
+ video_prompt = _append_generation_constraints(
558
+ f"Título: {title}\n\nRoteiro:\n{str(script.get('content') or '')[:12000]}",
559
+ kind="video",
560
+ )
558
561
  video_path = generate_video_from_pool(settings, video_prompt)
559
562
  except MediaGenerationError as exc:
560
563
  raise PipelineError(f"Pool de vídeo externo: {exc}") from exc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.3.45",
3
+ "version": "0.3.46",
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",