@danhachuel/thunderbolt 0.2.42 → 0.2.43
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 +25 -11
- package/README.md +14 -6
- package/app/main.py +38 -4
- package/hermes_ui/storage.py +1 -1
- package/integrations/openai_model_discovery.py +88 -0
- 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.2.
|
|
5
|
+
> **Versão deste manual:** 0.2.43
|
|
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.
|
|
103
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.43 install
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
Linux/macOS:
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
|
-
npx --yes @danhachuel/thunderbolt@0.2.
|
|
109
|
+
npx --yes @danhachuel/thunderbolt@0.2.43 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.
|
|
@@ -354,6 +354,9 @@ Na primeira execução, a barra lateral apresenta **Início**, **Niche Finder**,
|
|
|
354
354
|
| YouTube Data API Key (opcional) | Permitir o método oficial Data API para métricas públicas; não é necessária para a página pública nem para OAuth |
|
|
355
355
|
| YouTube OAuth Client ID | Identificar a aplicação OAuth 2.0 do Google para autorizar operações autenticadas, como upload |
|
|
356
356
|
| YouTube OAuth Client Secret | Secret do mesmo cliente OAuth 2.0; não é uma Data API Key nem um token de acesso |
|
|
357
|
+
| OpenAI/ NVIDIA NIM API key | Credencial do OpenAI ou NVIDIA Build/NIM, guardada apenas no storage local |
|
|
358
|
+
| OpenAI/ NVIDIA NIM Base URL | Endpoint OpenAI-compatible; por padrão `https://integrate.api.nvidia.com/v1` |
|
|
359
|
+
| OpenAI/ NVIDIA NIM modelo | Selector carregado de `/models` ou campo manual de fallback; guardado como `openai_model_name` |
|
|
357
360
|
| YouTube upload principal | Lógica do `youtube-automation-agent` adaptada e executada dentro do Thunderbolt |
|
|
358
361
|
| OAuth directo de redundância | Caminho alternativo accionado automaticamente se o agente falhar |
|
|
359
362
|
| TikTok Client ID/Secret | Credenciais da aplicação; Redirect URI, scopes e autorização ficam no TikTok for Developers Playground |
|
|
@@ -366,6 +369,16 @@ Na primeira execução, a barra lateral apresenta **Início**, **Niche Finder**,
|
|
|
366
369
|
|
|
367
370
|
As credenciais devem ser inseridas apenas na configuração local. A Kaggle API Key pertence exclusivamente à alternativa Kaggle. O Apify API Token pertence exclusivamente à alternativa Apify e não é usado para Kaggle; os resultados e histórico Apify ficam separados em `niche_apify_runs.json`. A Data API Key, o OAuth Client ID e o OAuth Client Secret são valores diferentes; Client ID + Secret não geram uma API Key nem um token OAuth até a conta ser autorizada. Não coloque nenhum deles no GitHub, no `package.json`, em blueprints ou em ficheiros de estado versionados.
|
|
368
371
|
|
|
372
|
+
## OpenAI/ NVIDIA NIM — API key, Base URL e modelo
|
|
373
|
+
|
|
374
|
+
A secção **LLM — providers e modelos** mantém o provider técnico `openai`, mas a área visual passou a chamar-se **OpenAI/ NVIDIA NIM — API key, Base URL e modelo**. Isto permite usar a API oficial da OpenAI, a API pública NVIDIA NIM/NVIDIA Build ou qualquer servidor local OpenAI-compatible sem alterar o restante fluxo do MoneyPrinterTurbo.
|
|
375
|
+
|
|
376
|
+
Para usar NVIDIA NIM, abra **Configurações > Configurações Técnicas**, mantenha ou preencha a Base URL `https://integrate.api.nvidia.com/v1` e introduza a API key do NVIDIA Build. Clique em **Consultar/actualizar modelos NIM**. O Thunderbolt executa a consulta apenas nesse momento, acrescentando `/models` à Base URL, envia a API key como `Authorization: Bearer ...` e interpreta a resposta OpenAI-compatible `data[].id`. Os IDs devolvidos aparecem num selector; ao escolher um, o valor é guardado em `openai_model_name` e continua a ser enviado para o `config.toml` do MoneyPrinterTurbo.
|
|
377
|
+
|
|
378
|
+
Se utilizar um NIM local, substitua a Base URL pelo endereço do serviço, por exemplo `http://127.0.0.1:8000/v1`. Se o serviço não tiver `/models`, estiver indisponível ou recusar a credencial, o erro aparece na própria UI sem mostrar a API key. Nesse caso, seleccione **Escrever modelo manualmente** ou utilize directamente o campo de modelo manual. A consulta não é executada automaticamente ao abrir Configurações Técnicas.
|
|
379
|
+
|
|
380
|
+
A API key não é gravada em logs, não é incluída no GitHub e não é enviada ao abrir a página. Os identificadores mostrados no selector são os IDs retornados pelo endpoint; para NVIDIA Build, normalmente não devem receber o prefixo interno `nvidia_nim/`, pois o endpoint OpenAI-compatible espera o ID original do modelo.
|
|
381
|
+
|
|
369
382
|
## 9. Testar as áreas principais
|
|
370
383
|
|
|
371
384
|
Após iniciar a aplicação, valide o seguinte percurso:
|
|
@@ -380,13 +393,14 @@ Após iniciar a aplicação, valide o seguinte percurso:
|
|
|
380
393
|
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.
|
|
381
394
|
9. **Edição > Limpador de Metadados:** 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 das páginas de criação não são aceites nesta área.
|
|
382
395
|
10. **Configurações > Configurações Técnicas:** confirme que os caminhos e credenciais estão locais e não aparecem no Git. Na secção **Contas Google/YouTube — canais em lote**, adicione cada conta com o seu e-mail/Gmail, OAuth Client ID e OAuth Client Secret próprios; o e-mail identifica a conta e não é usado para ler a caixa Gmail.
|
|
383
|
-
11. **
|
|
384
|
-
12. **
|
|
385
|
-
13. **
|
|
386
|
-
14. **
|
|
387
|
-
15. **Configurações >
|
|
388
|
-
16. **Configurações > MCP >
|
|
389
|
-
17. **Configurações > MCP >
|
|
396
|
+
11. **OpenAI/ NVIDIA NIM:** em **LLM — providers e modelos**, seleccione `openai`, confirme a Base URL, introduza a API key e clique em **Consultar/actualizar modelos NIM**. Escolha um modelo da lista ou utilize o fallback manual e guarde as configurações. Confirme que a consulta só ocorre após o clique e que um erro de endpoint não interrompe a UI.
|
|
397
|
+
12. **Criação de Vídeos:** 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`.
|
|
398
|
+
13. **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. Inicie o Thunderbolt pelo launcher, confirme o aviso verde **Worker activo** e verifique que o relógio apresentado corresponde ao computador. O worker cria no máximo um lote por canal por dia quando o horário local coincide.
|
|
399
|
+
14. **Configurações > Configurações Técnicas > Teste de vozes:** teste Edge/Azure ou provider configurado e confirme reprodução/download sem criação de tarefa.
|
|
400
|
+
15. **Upload directo:** em **Configurações > Configurações Técnicas > Contas Google/YouTube — canais em lote**, seleccione cada conta Google e carregue o seu ficheiro de cookies no painel **Upload directo — sessão YouTube Frontend API**. São aceites JSON de navegador, lista JSON e formato Netscape; o Thunderbolt valida `SID`, `SSID`, `HSID`, `APISID` e `SAPISID` e guarda o ficheiro em `storage/youtube_direct_accounts/<id-da-conta>/cookies.json`. Preencha também o `sessionInfo token` da mesma conta. Em **Canais > Upload directo — conta e canal**, associe o canal à conta e preencha o `DELEGATED_SESSION_ID` individual. Configure `INNERTUBE_API_KEY`, teste apenas com um vídeo de validação e confirme que o botão só fica disponível quando os três dados — cookies, sessionInfo e ID delegado — estão configurados. O método envia chunks de 256 KiB e não extrai cookies do navegador.
|
|
401
|
+
16. **Configurações > MCP > Client MCP:** confirme que Short Video Maker, AutoVio, OpenMontage e OpenCut aparecem com as portas padrão editáveis. O estado **Activo** é uma preferência local; a detecção deve indicar **Não detectado** quando os serviços externos não estiverem instalados ou iniciados.
|
|
402
|
+
17. **Configurações > MCP > Servidor MCP:** abra a subaba, mantenha o host `127.0.0.1`, active **Servidor MCP ON** e clique em **Guardar e iniciar Servidor MCP**. Confirme o endpoint `/mcp` e o health endpoint `/health`. Mantenha **Permitir ferramentas de escrita** desactivado até precisar que um agente crie lotes.
|
|
403
|
+
18. **Configurações > MCP > Skill:** clique em **Guardar skill localmente** e confirme o ficheiro em `storage/skills/moneyprinterturbo-video.md`; opcionalmente use **Descarregar skill .md** para obter a cópia através do navegador.
|
|
390
404
|
|
|
391
405
|
## Canais em lote por conta Google/YouTube
|
|
392
406
|
|
|
@@ -404,7 +418,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
|
|
|
404
418
|
|
|
405
419
|
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.
|
|
406
420
|
|
|
407
|
-
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.
|
|
421
|
+
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.43 install`; o instalador detecta e reutiliza o que já estiver válido.
|
|
408
422
|
|
|
409
423
|
### Niche Finder Apify
|
|
410
424
|
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ A primeira versão implementa a camada UI independente com:
|
|
|
22
22
|
| Edição | Menu expansível abaixo de Automação com Limpador de Metadados funcional, Cortes reservado e Editor Python inspirado no PYEdit para vídeos e scripts locais |
|
|
23
23
|
| Upload | YouTube via `youtube-automation-agent` adaptado internamente, OAuth directo de redundância, Upload directo via sessão Frontend API com cookies/sessionInfo por conta e DELEGATED_SESSION_ID por canal, TikTok, Instagram e Facebook Pages no front end |
|
|
24
24
|
| MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
|
|
25
|
-
| Configurações Técnicas | Provedores LLM, TTS/voz, preview de vozes, Suno, materiais, Whisper, FFmpeg, OAuth YouTube, contas Google/YouTube para canais em lote com e-mail, Client ID e Client Secret por conta, cookies SID/SSID/HSID/APISID/SAPISID e sessionInfo por conta, Data API Key opcional, Kaggle Username/API Key, Apify API Token/Actor ID/limites, Upload directo, TikTok Client ID/Secret e Upload-Post |
|
|
25
|
+
| Configurações Técnicas | Provedores LLM, OpenAI/NVIDIA NIM com API key, Base URL e selector de modelos, TTS/voz, preview de vozes, Suno, materiais, Whisper, FFmpeg, OAuth YouTube, contas Google/YouTube para canais em lote com e-mail, Client ID e Client Secret por conta, cookies SID/SSID/HSID/APISID/SAPISID e sessionInfo por conta, Data API Key opcional, Kaggle Username/API Key, Apify API Token/Actor ID/limites, Upload directo, TikTok Client ID/Secret e Upload-Post |
|
|
26
26
|
| Launcher | Execução via `npx`, instalação assistida, diagnóstico e preparação para distribuição |
|
|
27
27
|
|
|
28
28
|
## Upload directo — credenciais por conta e por canal
|
|
@@ -36,10 +36,18 @@ Os dados são segredos de sessão. Os valores não aparecem em tabelas ou logs,
|
|
|
36
36
|
|
|
37
37
|
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.
|
|
38
38
|
|
|
39
|
-
## Navegação da UI 0.2.
|
|
39
|
+
## Navegação da UI 0.2.43
|
|
40
40
|
|
|
41
41
|
A barra lateral mantém os níveis principais, nesta ordem: **Início**, **Niche Finder**, **Pipeline**, **Automação**, **Edição**, **Models AI** e **Configurações**. **Pipeline** é expansível e contém **Criação de Vídeos**, **Criação de Músicas** e **Upload**. **Edição** é expansível e contém **Limpador de Metadados**, **Cortes** e **Editor Python**, nessa ordem. **Models AI** é expansível e contém **Personagens** e **Redes Sociais**, nessa ordem. **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.
|
|
42
42
|
|
|
43
|
+
## OpenAI/ NVIDIA NIM — descoberta de modelos
|
|
44
|
+
|
|
45
|
+
Em **Configurações Técnicas > LLM — providers e modelos**, a área anteriormente chamada **OpenAI — API key, Base URL e modelo** passa a chamar-se **OpenAI/ NVIDIA NIM — API key, Base URL e modelo**. O provider interno continua a ser `openai`, para preservar a compatibilidade com o MoneyPrinterTurbo e com os restantes endpoints OpenAI-compatible.
|
|
46
|
+
|
|
47
|
+
A Base URL predefinida para NVIDIA NIM é `https://integrate.api.nvidia.com/v1`. Depois de inserir a API key, clique em **Consultar/actualizar modelos NIM**. O Thunderbolt consulta a Base URL acrescentando `/models`, lê os identificadores do formato OpenAI (`data[].id`) e apresenta-os num selector. O modelo escolhido é guardado em `openai_model_name` e é sincronizado para o `config.toml` do motor.
|
|
48
|
+
|
|
49
|
+
A consulta só é executada quando o utilizador carrega no botão; a aplicação não envia a API key ao abrir a página. Se o endpoint estiver offline, recusar a credencial ou não disponibilizar `/models`, a UI mostra o erro sem expor a API key e mantém a opção **Escrever modelo manualmente**. Também é possível manter um endpoint local NIM ou outro servidor OpenAI-compatible substituindo a Base URL.
|
|
50
|
+
|
|
43
51
|
## Instalação
|
|
44
52
|
|
|
45
53
|
Recomenda-se Python 3.11 ou superior e Node.js 18 ou superior.
|
|
@@ -125,9 +133,9 @@ thunderbolt
|
|
|
125
133
|
No Windows PowerShell, se `npx` for bloqueado por `npx.ps1`, use directamente `npx.cmd`:
|
|
126
134
|
|
|
127
135
|
```powershell
|
|
128
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
129
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
130
|
-
npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
136
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.43 install
|
|
137
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.43 doctor
|
|
138
|
+
npx.cmd --yes @danhachuel/thunderbolt@0.2.43
|
|
131
139
|
```
|
|
132
140
|
|
|
133
141
|
Como alternativa, pode permitir scripts para o seu utilizador:
|
|
@@ -151,7 +159,7 @@ O menu expansível **Niche Finder** contém duas alternativas independentes. **N
|
|
|
151
159
|
|
|
152
160
|
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.
|
|
153
161
|
|
|
154
|
-
**Niche Finder Apify** é a segunda alternativa e não usa o dataset, filtros, execução ou resultados Kaggle. Define três palavras-chave, período, limite de resultados, Shorts, duração, idioma de legendas e ordenação; depois de clicar em **Pesquisar no Apify**, inicia o actor `streamers~youtube-scraper`, acompanha o run, carrega o dataset, normaliza vídeos, limpa SRT, calcula VSC Ratio e tenta resumir as transcrições com o provider LLM configurado. Os resultados ficam na sessão própria `niche_apify_results`, o histórico pequeno fica em `storage/state/niche_apify_runs.json` e existem exportações JSON/CSV. Configure o **Apify API Token** em Configurações Técnicas. As dependências adicionais são instaladas pelo fluxo normal do pacote: `requests`, `pandas` e os componentes já existentes da análise. Em instalações existentes, execute novamente `npx.cmd --yes @danhachuel/thunderbolt@0.2.
|
|
162
|
+
**Niche Finder Apify** é a segunda alternativa e não usa o dataset, filtros, execução ou resultados Kaggle. Define três palavras-chave, período, limite de resultados, Shorts, duração, idioma de legendas e ordenação; depois de clicar em **Pesquisar no Apify**, inicia o actor `streamers~youtube-scraper`, acompanha o run, carrega o dataset, normaliza vídeos, limpa SRT, calcula VSC Ratio e tenta resumir as transcrições com o provider LLM configurado. Os resultados ficam na sessão própria `niche_apify_results`, o histórico pequeno fica em `storage/state/niche_apify_runs.json` e existem exportações JSON/CSV. Configure o **Apify API Token** em Configurações Técnicas. As dependências adicionais são instaladas pelo fluxo normal do pacote: `requests`, `pandas` e os componentes já existentes da análise. Em instalações existentes, execute novamente `npx.cmd --yes @danhachuel/thunderbolt@0.2.43 install`; o instalador detecta e reutiliza componentes já válidos.
|
|
155
163
|
|
|
156
164
|
## Editor Python baseado no PYEdit
|
|
157
165
|
|
package/app/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
import re
|
|
5
6
|
from datetime import date, datetime
|
|
@@ -38,6 +39,7 @@ from integrations.youtube_direct_credentials import COOKIE_KEYS as DIRECT_COOKIE
|
|
|
38
39
|
from integrations.youtube_batch import account_key as youtube_batch_account_key, account_status as youtube_batch_account_status, authorize_account as authorize_youtube_batch_account, delete_account_token as delete_youtube_batch_token, list_my_channels as list_youtube_batch_channels
|
|
39
40
|
from integrations.local_runtime import MoneyPrinterRuntime
|
|
40
41
|
from integrations.moneyprinter_config import sync_moneyprinter_config
|
|
42
|
+
from integrations.openai_model_discovery import DEFAULT_NVIDIA_NIM_BASE_URL, ModelDiscoveryError, fetch_openai_compatible_models
|
|
41
43
|
|
|
42
44
|
AI_STYLE_OPTIONS = [
|
|
43
45
|
"Natural Realista",
|
|
@@ -1595,14 +1597,36 @@ def render_settings():
|
|
|
1595
1597
|
with st.expander("LLM — providers e modelos", expanded=True):
|
|
1596
1598
|
provider_options = ["moonshot", "shengsuanyun", "openai", "gemini", "deepseek", "qwen", "azure", "volcengine", "grok", "minimax", "mimo", "cloudflare", "modelscope", "aihubmix", "aimlapi", "evolink", "ollama", "oneapi", "litellm", "groq", "pollinations"]
|
|
1597
1599
|
llm_provider = st.selectbox("LLM provider", provider_options, index=provider_options.index(settings.get("llm_provider", "moonshot")) if settings.get("llm_provider", "moonshot") in provider_options else 0)
|
|
1598
|
-
st.markdown("**OpenAI — API key, Base URL e modelo**")
|
|
1600
|
+
st.markdown("**OpenAI/ NVIDIA NIM — API key, Base URL e modelo**")
|
|
1601
|
+
st.caption("O provider interno continua a ser `openai`, mas pode usar qualquer endpoint OpenAI-compatible. Para NVIDIA NIM, a Base URL predefinida é `https://integrate.api.nvidia.com/v1`; o selector consulta `/models` e deixa um campo manual como fallback.")
|
|
1599
1602
|
openai_cols = st.columns(3)
|
|
1600
1603
|
with openai_cols[0]:
|
|
1601
|
-
openai_api_key = text_setting("OpenAI API key", "openai_api_key", secret=True)
|
|
1604
|
+
openai_api_key = text_setting("OpenAI/ NVIDIA NIM API key", "openai_api_key", secret=True, help_text="API key do OpenAI ou do NVIDIA Build/NIM. A credencial fica apenas no storage local.")
|
|
1602
1605
|
with openai_cols[1]:
|
|
1603
|
-
openai_base_url =
|
|
1606
|
+
openai_base_url = st.text_input("OpenAI/ NVIDIA NIM Base URL", value=str(settings.get("openai_base_url", "") or DEFAULT_NVIDIA_NIM_BASE_URL), help="Ex.: https://integrate.api.nvidia.com/v1. O Thunderbolt acrescenta /models para descobrir os modelos.", key="settings_openai_base_url")
|
|
1604
1607
|
with openai_cols[2]:
|
|
1605
|
-
|
|
1608
|
+
cached_catalog = st.session_state.get("openai_model_catalog", {})
|
|
1609
|
+
catalog_key = f"{openai_base_url.strip()}::{hashlib.sha256(openai_api_key.encode('utf-8')).hexdigest()}"
|
|
1610
|
+
cached_models = list(cached_catalog.get("models", [])) if cached_catalog.get("key") == catalog_key else []
|
|
1611
|
+
current_model_name = str(settings.get("openai_model_name", "") or "")
|
|
1612
|
+
manual_option = "__manual_model__"
|
|
1613
|
+
if cached_models:
|
|
1614
|
+
model_options = [manual_option, *cached_models]
|
|
1615
|
+
model_index = model_options.index(current_model_name) if current_model_name in model_options else 0
|
|
1616
|
+
selected_model = st.selectbox("Modelo OpenAI/ NVIDIA NIM", model_options, index=model_index, format_func=lambda value: "Escrever modelo manualmente" if value == manual_option else value, key="settings_openai_model_select")
|
|
1617
|
+
if selected_model == manual_option:
|
|
1618
|
+
openai_model_name = st.text_input("Modelo manual", value=current_model_name if current_model_name not in cached_models else "", help_text="Ex.: nvidia_nim/minimaxai/minimax-m3", key="settings_openai_model_manual")
|
|
1619
|
+
else:
|
|
1620
|
+
openai_model_name = selected_model
|
|
1621
|
+
else:
|
|
1622
|
+
openai_model_name = st.text_input("Modelo OpenAI/ NVIDIA NIM", value=current_model_name, help="Pode escrever um ID manualmente se o endpoint não disponibilizar /models.", key="settings_openai_model_name")
|
|
1623
|
+
refresh_openai_models = st.form_submit_button("Consultar/actualizar modelos NIM", use_container_width=True)
|
|
1624
|
+
if cached_catalog.get("key") == catalog_key and cached_catalog.get("error"):
|
|
1625
|
+
st.warning(str(cached_catalog["error"]))
|
|
1626
|
+
elif cached_models:
|
|
1627
|
+
st.caption(f"{len(cached_models)} modelo(s) carregado(s) a partir de {openai_base_url.rstrip('/')}/models.")
|
|
1628
|
+
else:
|
|
1629
|
+
st.info("Preencha a API key e clique em Consultar/actualizar modelos NIM para carregar os IDs disponíveis.")
|
|
1606
1630
|
llm_fields = [
|
|
1607
1631
|
("Moonshot / Kimi", "moonshot", True), ("Shengsuan Cloud", "shengsuanyun", True),
|
|
1608
1632
|
("Google Gemini", "gemini", True), ("DeepSeek", "deepseek", True), ("Alibaba Qwen", "qwen", True),
|
|
@@ -1676,6 +1700,16 @@ def render_settings():
|
|
|
1676
1700
|
upload_post_platforms = text_setting("Plataformas Upload-Post", "upload_post_platforms")
|
|
1677
1701
|
upload_post_auto_upload = st.checkbox("Publicar automaticamente após gerar", bool(settings.get("upload_post_auto_upload", False)))
|
|
1678
1702
|
|
|
1703
|
+
if refresh_openai_models:
|
|
1704
|
+
try:
|
|
1705
|
+
discovered_models = fetch_openai_compatible_models(openai_api_key, openai_base_url)
|
|
1706
|
+
st.session_state["openai_model_catalog"] = {"key": catalog_key, "models": discovered_models, "error": ""}
|
|
1707
|
+
st.success(f"{len(discovered_models)} modelo(s) carregado(s) do endpoint OpenAI-compatible.")
|
|
1708
|
+
st.rerun()
|
|
1709
|
+
except ModelDiscoveryError as exc:
|
|
1710
|
+
st.session_state["openai_model_catalog"] = {"key": catalog_key, "models": [], "error": str(exc)}
|
|
1711
|
+
st.rerun()
|
|
1712
|
+
|
|
1679
1713
|
if st.form_submit_button("Guardar configurações do Thunderbolt", type="primary"):
|
|
1680
1714
|
direct_cookie_error = ""
|
|
1681
1715
|
configured_direct_accounts = [account for account in settings.get("youtube_batch_accounts", []) if isinstance(account, dict) and account.get("id")]
|
package/hermes_ui/storage.py
CHANGED
|
@@ -100,7 +100,7 @@ DEFAULTS: dict[str, Any] = {
|
|
|
100
100
|
"shengsuanyun_base_url": "",
|
|
101
101
|
"shengsuanyun_model_name": "",
|
|
102
102
|
"openai_api_key": "",
|
|
103
|
-
"openai_base_url": "",
|
|
103
|
+
"openai_base_url": "https://integrate.api.nvidia.com/v1",
|
|
104
104
|
"openai_model_name": "",
|
|
105
105
|
"gemini_api_key": "",
|
|
106
106
|
"gemini_model_name": "",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Discovery of models from OpenAI-compatible providers such as NVIDIA NIM."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DEFAULT_NVIDIA_NIM_BASE_URL = "https://integrate.api.nvidia.com/v1"
|
|
11
|
+
DEFAULT_TIMEOUT_SECONDS = 12
|
|
12
|
+
MAX_MODEL_IDS = 2000
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelDiscoveryError(ValueError):
|
|
16
|
+
"""Raised when an OpenAI-compatible model list cannot be loaded safely."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def models_endpoint(base_url: str) -> str:
|
|
20
|
+
"""Return the ``/models`` endpoint for an OpenAI-compatible base URL."""
|
|
21
|
+
value = str(base_url or "").strip().rstrip("/")
|
|
22
|
+
if not value:
|
|
23
|
+
raise ModelDiscoveryError("Informe a Base URL antes de consultar os modelos.")
|
|
24
|
+
if value.endswith("/models"):
|
|
25
|
+
return value
|
|
26
|
+
return f"{value}/models"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def normalize_model_ids(payload: Any) -> list[str]:
|
|
30
|
+
"""Extract unique model IDs from the standard OpenAI response shape."""
|
|
31
|
+
if isinstance(payload, dict):
|
|
32
|
+
collection = payload.get("data")
|
|
33
|
+
if collection is None:
|
|
34
|
+
collection = payload.get("models")
|
|
35
|
+
elif isinstance(payload, list):
|
|
36
|
+
collection = payload
|
|
37
|
+
else:
|
|
38
|
+
collection = None
|
|
39
|
+
|
|
40
|
+
if not isinstance(collection, list):
|
|
41
|
+
raise ModelDiscoveryError("A resposta de modelos não tem uma lista data válida.")
|
|
42
|
+
|
|
43
|
+
model_ids: set[str] = set()
|
|
44
|
+
for item in collection:
|
|
45
|
+
if isinstance(item, dict):
|
|
46
|
+
model_id = item.get("id") or item.get("name")
|
|
47
|
+
elif isinstance(item, str):
|
|
48
|
+
model_id = item
|
|
49
|
+
else:
|
|
50
|
+
model_id = None
|
|
51
|
+
if isinstance(model_id, str) and model_id.strip():
|
|
52
|
+
model_ids.add(model_id.strip())
|
|
53
|
+
|
|
54
|
+
if not model_ids:
|
|
55
|
+
raise ModelDiscoveryError("O endpoint não devolveu nenhum identificador de modelo.")
|
|
56
|
+
return sorted(model_ids, key=str.casefold)[:MAX_MODEL_IDS]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def fetch_openai_compatible_models(
|
|
60
|
+
api_key: str,
|
|
61
|
+
base_url: str,
|
|
62
|
+
*,
|
|
63
|
+
timeout: float = DEFAULT_TIMEOUT_SECONDS,
|
|
64
|
+
) -> list[str]:
|
|
65
|
+
"""Fetch model IDs without exposing the API key in errors or logs."""
|
|
66
|
+
endpoint = models_endpoint(base_url)
|
|
67
|
+
headers = {"Accept": "application/json"}
|
|
68
|
+
token = str(api_key or "").strip()
|
|
69
|
+
if token:
|
|
70
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
response = requests.get(endpoint, headers=headers, timeout=timeout)
|
|
74
|
+
except requests.RequestException as exc:
|
|
75
|
+
raise ModelDiscoveryError(f"Não foi possível consultar {endpoint}: {exc}") from exc
|
|
76
|
+
|
|
77
|
+
if response.status_code >= 400:
|
|
78
|
+
if response.status_code in (401, 403):
|
|
79
|
+
raise ModelDiscoveryError(
|
|
80
|
+
f"O endpoint recusou a credencial (HTTP {response.status_code}). Verifique a API key."
|
|
81
|
+
)
|
|
82
|
+
raise ModelDiscoveryError(f"O endpoint devolveu HTTP {response.status_code}.")
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
payload = response.json()
|
|
86
|
+
except ValueError as exc:
|
|
87
|
+
raise ModelDiscoveryError("O endpoint devolveu uma resposta que não é JSON.") from exc
|
|
88
|
+
return normalize_model_ids(payload)
|
package/package.json
CHANGED