@danhachuel/thunderbolt 0.3.58 → 0.3.60
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 +1 -1
- package/app/influencers_ui.py +113 -109
- package/app/main.py +0 -2
- 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.3.
|
|
5
|
+
> **Versão deste manual:** 0.3.60
|
|
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)
|
|
@@ -114,13 +114,13 @@ Execute:
|
|
|
114
114
|
Windows PowerShell ou MobaXterm:
|
|
115
115
|
|
|
116
116
|
```powershell
|
|
117
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
117
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.60 install
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
Linux/macOS:
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
123
|
+
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.60 install
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
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.
|
|
@@ -128,7 +128,7 @@ A instalação normal é **segura para actualizações**: preserva `storage`, Bl
|
|
|
128
128
|
Se quiser apagar absolutamente tudo de forma intencional, use o comando destrutivo separado:
|
|
129
129
|
|
|
130
130
|
```powershell
|
|
131
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
131
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.60 install --purge-data
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
O parâmetro `--purge-data` apaga Blueprints, Brandings, configurações, storage e artefactos locais. Não o use numa actualização normal.
|
|
@@ -542,7 +542,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
|
|
|
542
542
|
|
|
543
543
|
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.
|
|
544
544
|
|
|
545
|
-
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.
|
|
545
|
+
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.60 install`; o instalador detecta e reutiliza o que já estiver válido.
|
|
546
546
|
|
|
547
547
|
### Niche Finder Apify
|
|
548
548
|
|
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ No topo da área principal da aplicação existe o menu nativo de idioma no padr
|
|
|
79
79
|
|
|
80
80
|
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.
|
|
81
81
|
|
|
82
|
-
## Navegação da UI 0.3.
|
|
82
|
+
## Navegação da UI 0.3.60
|
|
83
83
|
|
|
84
84
|
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**, **Geração de Conteúdo IA**, **Redes Sociais**, **Tutorial Meta** e **Tutorial Supabase**, nessa ordem. A página Geração de Conteúdo IA divide-se em **Imagens**, **Vídeos** e **Motion Control**; a subaba Motion Control permanece vazia nesta release.
|
|
85
85
|
**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.
|
package/app/influencers_ui.py
CHANGED
|
@@ -153,122 +153,126 @@ def render_ai_influencer_characters(
|
|
|
153
153
|
if repository is None:
|
|
154
154
|
return
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
with
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
"
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
new_character_tab, created_characters_tab = st.tabs(["Novo personagem", "Personagens criados"])
|
|
157
|
+
|
|
158
|
+
with new_character_tab:
|
|
159
|
+
with st.form("influencer_create_form", clear_on_submit=True):
|
|
160
|
+
st.subheader("Novo personagem")
|
|
161
|
+
cols = st.columns(3)
|
|
162
|
+
with cols[0]:
|
|
163
|
+
name = st.text_input("Nome do personagem", key="influencer_new_name")
|
|
164
|
+
with cols[1]:
|
|
165
|
+
language = st.selectbox(
|
|
166
|
+
"Idioma",
|
|
167
|
+
language_options,
|
|
168
|
+
index=language_index(st.session_state.get("influencer_new_language", "pt")),
|
|
169
|
+
format_func=language_formatter,
|
|
170
|
+
key="influencer_new_language",
|
|
171
|
+
)
|
|
172
|
+
with cols[2]:
|
|
173
|
+
instagram_id = st.text_input("Instagram Business ID (opcional)", key="influencer_new_instagram_id")
|
|
174
|
+
bio = st.text_area("Biografia e instruções", height=130, key="influencer_new_bio")
|
|
175
|
+
files = st.file_uploader(
|
|
176
|
+
"Imagens e documentos de referência",
|
|
177
|
+
type=[item.lstrip(".") for item in sorted({".png", ".jpg", ".jpeg", ".webp", ".gif", ".md", ".json"})],
|
|
178
|
+
accept_multiple_files=True,
|
|
179
|
+
key="influencer_new_assets",
|
|
180
|
+
help="Pode seleccionar várias imagens e ficheiros .md/.json no mesmo upload.",
|
|
168
181
|
)
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
bio = st.text_area("Biografia e instruções", height=130, key="influencer_new_bio")
|
|
172
|
-
files = st.file_uploader(
|
|
173
|
-
"Imagens e documentos de referência",
|
|
174
|
-
type=[item.lstrip(".") for item in sorted({".png", ".jpg", ".jpeg", ".webp", ".gif", ".md", ".json"})],
|
|
175
|
-
accept_multiple_files=True,
|
|
176
|
-
key="influencer_new_assets",
|
|
177
|
-
help="Pode seleccionar várias imagens e ficheiros .md/.json no mesmo upload.",
|
|
178
|
-
)
|
|
179
|
-
create = st.form_submit_button("Guardar personagem", type="primary", use_container_width=True)
|
|
180
|
-
if create:
|
|
181
|
-
try:
|
|
182
|
-
record = repository.create_influencer({"name": name, "bio": bio, "language": language, "instagram_business_id": instagram_id})
|
|
183
|
-
saved_assets = 0
|
|
184
|
-
for uploaded in files or []:
|
|
185
|
-
repository.save_asset(record["id"], uploaded.name, uploaded.getvalue())
|
|
186
|
-
saved_assets += 1
|
|
187
|
-
st.session_state["influencer_selected_id"] = record["id"]
|
|
188
|
-
st.success(f"Personagem guardado com {saved_assets} asset(s) de referência.")
|
|
189
|
-
st.rerun()
|
|
190
|
-
except (ValueError, InfluencerBackendError) as exc:
|
|
191
|
-
st.error(str(exc))
|
|
192
|
-
except Exception:
|
|
193
|
-
st.error("Não foi possível guardar o personagem ou os assets seleccionados.")
|
|
194
|
-
|
|
195
|
-
try:
|
|
196
|
-
influencers = repository.list_influencers()
|
|
197
|
-
except Exception:
|
|
198
|
-
st.error("Não foi possível consultar os personagens no backend seleccionado.")
|
|
199
|
-
return
|
|
200
|
-
if not influencers:
|
|
201
|
-
st.info("Ainda não existem personagens. Crie o primeiro acima.")
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
options = _influencer_options(influencers)
|
|
205
|
-
current = str(st.session_state.get("influencer_selected_id") or options[0])
|
|
206
|
-
selected_id = st.selectbox("Personagem seleccionado", options, index=options.index(current) if current in options else 0, format_func=lambda value: _influencer_label(influencers, value), key="influencer_selected_id")
|
|
207
|
-
selected = next(item for item in influencers if str(item.get("id")) == selected_id)
|
|
208
|
-
|
|
209
|
-
with st.expander("Editar perfil", expanded=False):
|
|
210
|
-
with st.form(f"influencer_edit_form_{selected_id}"):
|
|
211
|
-
edit_name = st.text_input("Nome", value=str(selected.get("name") or ""), key=f"influencer_edit_name_{selected_id}")
|
|
212
|
-
edit_language = st.selectbox(
|
|
213
|
-
"Idioma",
|
|
214
|
-
language_options,
|
|
215
|
-
index=language_index(selected.get("language") or "pt"),
|
|
216
|
-
format_func=language_formatter,
|
|
217
|
-
key=f"influencer_edit_language_{selected_id}",
|
|
218
|
-
)
|
|
219
|
-
edit_instagram = st.text_input("Instagram Business ID", value=str(selected.get("instagram_business_id") or ""), key=f"influencer_edit_instagram_{selected_id}")
|
|
220
|
-
edit_bio = st.text_area("Biografia e instruções", value=str(selected.get("bio") or ""), height=130, key=f"influencer_edit_bio_{selected_id}")
|
|
221
|
-
save_edit = st.form_submit_button("Guardar alterações", type="primary")
|
|
222
|
-
if save_edit:
|
|
182
|
+
create = st.form_submit_button("Guardar personagem", type="primary", use_container_width=True)
|
|
183
|
+
if create:
|
|
223
184
|
try:
|
|
224
|
-
repository.
|
|
225
|
-
|
|
185
|
+
record = repository.create_influencer({"name": name, "bio": bio, "language": language, "instagram_business_id": instagram_id})
|
|
186
|
+
saved_assets = 0
|
|
187
|
+
for uploaded in files or []:
|
|
188
|
+
repository.save_asset(record["id"], uploaded.name, uploaded.getvalue())
|
|
189
|
+
saved_assets += 1
|
|
190
|
+
st.session_state["influencer_selected_id"] = record["id"]
|
|
191
|
+
st.success(f"Personagem guardado com {saved_assets} asset(s) de referência.")
|
|
226
192
|
st.rerun()
|
|
227
|
-
except
|
|
193
|
+
except (ValueError, InfluencerBackendError) as exc:
|
|
228
194
|
st.error(str(exc))
|
|
195
|
+
except Exception:
|
|
196
|
+
st.error("Não foi possível guardar o personagem ou os assets seleccionados.")
|
|
229
197
|
|
|
230
|
-
with
|
|
231
|
-
more_files = st.file_uploader(
|
|
232
|
-
"Adicionar imagens/documentos de referência",
|
|
233
|
-
type=[item.lstrip(".") for item in sorted({".png", ".jpg", ".jpeg", ".webp", ".gif", ".md", ".json"})],
|
|
234
|
-
accept_multiple_files=True,
|
|
235
|
-
key=f"influencer_append_assets_{selected_id}",
|
|
236
|
-
)
|
|
237
|
-
append_assets = st.form_submit_button("Adicionar assets ao personagem", use_container_width=True)
|
|
238
|
-
if append_assets:
|
|
198
|
+
with created_characters_tab:
|
|
239
199
|
try:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
st.
|
|
246
|
-
|
|
247
|
-
|
|
200
|
+
influencers = repository.list_influencers()
|
|
201
|
+
except Exception:
|
|
202
|
+
st.error("Não foi possível consultar os personagens no backend seleccionado.")
|
|
203
|
+
return
|
|
204
|
+
if not influencers:
|
|
205
|
+
st.info("Ainda não existem personagens. Crie o primeiro na subaba Novo personagem.")
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
options = _influencer_options(influencers)
|
|
209
|
+
current = str(st.session_state.get("influencer_selected_id") or options[0])
|
|
210
|
+
selected_id = st.selectbox("Personagem seleccionado", options, index=options.index(current) if current in options else 0, format_func=lambda value: _influencer_label(influencers, value), key="influencer_selected_id")
|
|
211
|
+
selected = next(item for item in influencers if str(item.get("id")) == selected_id)
|
|
212
|
+
|
|
213
|
+
with st.expander(f"Card do personagem · {selected.get('name') or selected_id}", expanded=False):
|
|
214
|
+
with st.form(f"influencer_edit_form_{selected_id}"):
|
|
215
|
+
edit_name = st.text_input("Nome", value=str(selected.get("name") or ""), key=f"influencer_edit_name_{selected_id}")
|
|
216
|
+
edit_language = st.selectbox(
|
|
217
|
+
"Idioma",
|
|
218
|
+
language_options,
|
|
219
|
+
index=language_index(selected.get("language") or "pt"),
|
|
220
|
+
format_func=language_formatter,
|
|
221
|
+
key=f"influencer_edit_language_{selected_id}",
|
|
222
|
+
)
|
|
223
|
+
edit_instagram = st.text_input("Instagram Business ID", value=str(selected.get("instagram_business_id") or ""), key=f"influencer_edit_instagram_{selected_id}")
|
|
224
|
+
edit_bio = st.text_area("Biografia e instruções", value=str(selected.get("bio") or ""), height=130, key=f"influencer_edit_bio_{selected_id}")
|
|
225
|
+
save_edit = st.form_submit_button("Guardar alterações", type="primary")
|
|
226
|
+
if save_edit:
|
|
227
|
+
try:
|
|
228
|
+
repository.update_influencer(selected_id, {"name": edit_name, "language": edit_language, "instagram_business_id": edit_instagram, "bio": edit_bio})
|
|
229
|
+
st.success("Perfil do personagem actualizado.")
|
|
230
|
+
st.rerun()
|
|
231
|
+
except Exception as exc:
|
|
232
|
+
st.error(str(exc))
|
|
233
|
+
|
|
234
|
+
with st.form(f"influencer_append_assets_form_{selected_id}"):
|
|
235
|
+
more_files = st.file_uploader(
|
|
236
|
+
"Adicionar imagens/documentos de referência",
|
|
237
|
+
type=[item.lstrip(".") for item in sorted({".png", ".jpg", ".jpeg", ".webp", ".gif", ".md", ".json"})],
|
|
238
|
+
accept_multiple_files=True,
|
|
239
|
+
key=f"influencer_append_assets_{selected_id}",
|
|
240
|
+
)
|
|
241
|
+
append_assets = st.form_submit_button("Adicionar assets ao personagem", use_container_width=True)
|
|
242
|
+
if append_assets:
|
|
243
|
+
try:
|
|
244
|
+
saved_assets = 0
|
|
245
|
+
for uploaded in more_files or []:
|
|
246
|
+
repository.save_asset(selected_id, uploaded.name, uploaded.getvalue())
|
|
247
|
+
saved_assets += 1
|
|
248
|
+
st.success(f"{saved_assets} asset(s) adicionado(s).")
|
|
249
|
+
st.rerun()
|
|
250
|
+
except Exception as exc:
|
|
251
|
+
st.error(str(exc))
|
|
248
252
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
253
|
+
try:
|
|
254
|
+
assets = repository.list_assets(selected_id)
|
|
255
|
+
except Exception:
|
|
256
|
+
assets = []
|
|
257
|
+
st.subheader(f"Assets de referência · {selected.get('name') or selected_id}")
|
|
258
|
+
if not assets:
|
|
259
|
+
st.info("Este personagem ainda não tem imagens ou documentos de referência.")
|
|
260
|
+
else:
|
|
261
|
+
cols = st.columns(min(4, max(1, len(assets))))
|
|
262
|
+
for index, asset in enumerate(assets):
|
|
263
|
+
with cols[index % len(cols)]:
|
|
264
|
+
st.caption(_asset_label(asset))
|
|
265
|
+
path = _local_asset_path(asset)
|
|
266
|
+
if str(asset.get("asset_type") or "") == "image" and path:
|
|
267
|
+
st.image(str(path), use_container_width=True)
|
|
268
|
+
elif str(asset.get("asset_type") or "") == "document":
|
|
269
|
+
raw = str(asset.get("document_json") or "")
|
|
270
|
+
try:
|
|
271
|
+
preview = json.loads(raw) if raw else {}
|
|
272
|
+
st.json(preview, expanded=False)
|
|
273
|
+
except (TypeError, json.JSONDecodeError):
|
|
274
|
+
st.code(raw[:1600], language="markdown")
|
|
275
|
+
st.caption(f"{asset.get('size_bytes', 0)} bytes · {asset.get('mime_type') or 'unknown'}")
|
|
272
276
|
|
|
273
277
|
|
|
274
278
|
def _render_content_history(repository: Any, influencer_id: str = "") -> None:
|
package/app/main.py
CHANGED
|
@@ -5458,8 +5458,6 @@ def render_settings():
|
|
|
5458
5458
|
|
|
5459
5459
|
render_media_provider_cards(settings, embedded=True)
|
|
5460
5460
|
|
|
5461
|
-
st.info("A configuração do backend AI Influencers, incluindo o selector entre SQLite e Supabase e as credenciais, está na aba AI Influencers.")
|
|
5462
|
-
|
|
5463
5461
|
with st.expander("Voz, TTS e música — Azure Speech, restantes serviços e Suno", expanded=False):
|
|
5464
5462
|
st.caption("Cada serviço está separado no seu próprio cartão. Os botões de teste ficam dentro do cartão correspondente e fazem apenas diagnóstico, sem gerar áudio ou música.")
|
|
5465
5463
|
|
package/package.json
CHANGED