@danhachuel/thunderbolt 0.3.70 → 0.3.72
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 +19 -10
- package/README.md +23 -11
- package/THIRD-PARTY-NOTICES.md +9 -0
- package/app/influencers_ui.py +183 -8
- package/app/main.py +206 -10
- package/hermes_ui/creative_generation.py +39 -0
- package/hermes_ui/influencers.py +19 -0
- package/hermes_ui/media_generation.py +335 -0
- package/integrations/bilibili_upload.py +259 -0
- package/integrations/distrokid_upload.py +227 -0
- package/package.json +1 -1
- package/requirements.txt +2 -0
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.72
|
|
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)
|
|
@@ -35,9 +35,13 @@ O backend local predefinido é **SQLite**, pelo que Personagens e Geração de C
|
|
|
35
35
|
|
|
36
36
|
Se pretender trabalhar sem serviço externo, seleccione **SQLite** no selector da aba **Configuração API > AI Influencers**. O caminho `storage/state/ai_influencers.db` e a pasta `storage/influencers/` são geridos internamente, sem campos editáveis para o utilizador. Apenas um backend é usado de cada vez. A base fica na pasta persistente do Thunderbolt, fora da instalação temporária do pacote npm; numa actualização normal, personagens, assets e configurações são preservados. O instalador também procura uma base `ai_influencers.db` válida em instalações anteriores do cache npm e recupera-a sem substituir uma base persistente já existente.
|
|
37
37
|
|
|
38
|
-
Em **AI Influencers > Geração de Conteúdo IA**, utilize as subabas **Imagens
|
|
38
|
+
Em **AI Influencers > Geração de Conteúdo IA**, utilize as subabas **Imagens** e **Vídeos**. Seleccione um personagem, prompt e provider/modelo configurado no pool correspondente. A subaba **Imagens** usa o pool de imagem configurado em **Imagem e Video IA**. A subaba **Vídeos** requer uma imagem inicial e usa o pool de vídeo; KIE AI, Replicate e FAL AI podem usar tarefas assíncronas. O Thunderbolt consulta o estado e guarda o resultado local antes da revisão. Na Replicate, o campo Modelo deve ser o identificador do modelo ou da versão, e os inputs específicos dependem do modelo configurado.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Em **AI Influencers > Motion Control**, carregue um vídeo original `.mp4` ou `.mov` entre 3 e 30 segundos e uma imagem de referência `.jpg`, `.jpeg` ou `.png` até 10 MB. O prompt é opcional e limitado a 2500 caracteres. O Thunderbolt valida os limites, guarda os originais no storage local, envia-os ao upload temporário oficial KIE, cria `kling-2.6/motion-control`, consulta `/api/v1/jobs/recordInfo` e descarrega o MP4 final localmente. É necessário um cartão KIE AI activo no pool de vídeo. O workflow não utiliza callback público, Telegram, Postiz, Google Drive ou publicação social.
|
|
41
|
+
|
|
42
|
+
Em **AI Influencers > UGC Products**, carregue a imagem do produto e preencha **Roteiro de vídeo**. Se o roteiro contiver dois blocos separados por `---`, os blocos são usados directamente; nos restantes casos, o pool LLM cria dois prompts de oito segundos, mantendo o produto e proibindo alterações físicas, texto incorporado, legendas e watermark. O Thunderbolt usa o endpoint KIE VEO3.1 `/api/v1/veo/generate` com `veo3_fast`, uma `imageUrls`, `duration: 8` e `resolution: 720p`, consulta `/api/v1/veo/record-info`, descarrega os dois clips e junta-os localmente com FFmpeg. O resultado final e os IDs das tarefas são persistidos no backend AI Influencers; não há Telegram, Postiz, Drive, upload ou publicação social.
|
|
43
|
+
|
|
44
|
+
A geração standalone guarda prompt, provider, modelo, estado, IDs das tarefas, inputs e caminho do artefacto em conteúdos internos do backend. O proprietário técnico desses registos fica oculto da lista de personagens. Os destinos sociais não são apresentados nestes dois formulários e a publicação permanece fora do workflow.
|
|
41
45
|
|
|
42
46
|
O runtime do Thunderbolt adapta o comportamento dos workflows do episódio 35, mas não instala nem executa n8n. Formulários, Data Tables, Switch, Wait e subworkflows são substituídos por páginas Streamlit, repository Supabase/SQLite, adapters multimédia, worker, logs e notificações. Nunca coloque API keys nos JSONs dos workflows, em ficheiros versionados ou em mensagens de diagnóstico.
|
|
43
47
|
|
|
@@ -50,7 +54,7 @@ A execução segue sempre **Tema → Script → Título → Keywords (opcional)
|
|
|
50
54
|
| Opção visível | Rota executada | Resultado esperado |
|
|
51
55
|
|---|---|---|
|
|
52
56
|
| Pexels/Pixabay | MoneyPrinterTurbo stock com a fonte efectiva e as chaves correspondentes | MP4 composto localmente |
|
|
53
|
-
| Full IA | Pool de vídeo limitado
|
|
57
|
+
| Full IA | Pool de vídeo limitado pelos cartões activos que declaram capacidade | MP4 gerado pelo provider activo |
|
|
54
58
|
| Apenas Música | Áudio local/Suno já preparado | Ficheiro musical pronto para um upload de música; não gera MP4, thumbnail nem upload YouTube |
|
|
55
59
|
|
|
56
60
|
Antes de iniciar a rota stock, confirme em **Configurações > Configuração API > Fontes de materiais** que existe pelo menos uma API key para a fonte efectiva. Sem uma key Pexels/Pixabay, o erro é apresentado como configuração da fonte, em vez de uma falha genérica de vídeo. Se o MoneyPrinterTurbo devolver `MPT_NEEDS_INPUT`, o Thunderbolt lê `LLM_PROVIDER`, `MISSING` e `INVALID` e mostra no erro todas as APIs afectadas, como **OpenAI / NVIDIA NIM API + Pexels API**, incluindo os campos que faltam. **Google Lyria não é apresentado como implementado nesta release**; a rota Apenas Música aceita áudio local e a integração Suno existente.
|
|
@@ -128,13 +132,13 @@ Execute:
|
|
|
128
132
|
Windows PowerShell ou MobaXterm:
|
|
129
133
|
|
|
130
134
|
```powershell
|
|
131
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
135
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.72 install
|
|
132
136
|
```
|
|
133
137
|
|
|
134
138
|
Linux/macOS:
|
|
135
139
|
|
|
136
140
|
```bash
|
|
137
|
-
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
141
|
+
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.72 install
|
|
138
142
|
```
|
|
139
143
|
|
|
140
144
|
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.
|
|
@@ -142,7 +146,7 @@ A instalação normal é **segura para actualizações**: preserva `storage`, Bl
|
|
|
142
146
|
Se quiser apagar absolutamente tudo de forma intencional, use o comando destrutivo separado:
|
|
143
147
|
|
|
144
148
|
```powershell
|
|
145
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
149
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.72 install --purge-data
|
|
146
150
|
```
|
|
147
151
|
|
|
148
152
|
O parâmetro `--purge-data` apaga Blueprints, Brandings, configurações, storage e artefactos locais. Não o use numa actualização normal.
|
|
@@ -481,7 +485,7 @@ O **Backlog Vídeos** e a lista **Automação > Automação Youtube > Vídeos ca
|
|
|
481
485
|
|
|
482
486
|
Nos dois cards, o bloco de estado apresenta o valor técnico, o rótulo legível, a barra de progresso e a mensagem de erro quando existir. O bloco **Formato** resolve nesta ordem `format`, `style_wide`, `style` e, por fim, `wide`, mantendo a apresentação de formatos como `wide`, `shorts`, `music` ou `full_ia` mesmo em tarefas antigas. Em **Automação Youtube > Vídeos cadastrados**, o botão **Start** retoma a partir dos artefactos persistidos: reutiliza roteiro, título/keywords, vídeo, prompt e thumbnail prontos e só executa novamente uma etapa cujo resultado não exista. O botão **Apagar** remove o card da fila depois de confirmação e preserva os ficheiros de artefactos; uma tarefa em execução deve ser parada antes da remoção.
|
|
483
487
|
|
|
484
|
-
### Upload Música — JewelMusic, Pushtunes e
|
|
488
|
+
### Upload Música — JewelMusic, Pushtunes, ytmusicapi e DistroKid
|
|
485
489
|
|
|
486
490
|
A área **Pipeline Música > Upload Música** separa três métodos com contratos diferentes. Em **JewelMusic**, active a integração, introduza a API Key fornecida pelo dashboard da JewelMusic e confirme a Base URL oficial `https://api.jewelmusic.com` e, se necessário, configure proxy e timeout. Carregue ou seleccione um ficheiro de música, indique artista e título e clique em **Enviar música para JewelMusic**. O teste de ligação consulta `/v1/ping`; o upload envia `multipart/form-data` para `/v1/tracks/upload` com os metadados preenchidos.
|
|
487
491
|
|
|
@@ -570,7 +574,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
|
|
|
570
574
|
|
|
571
575
|
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.
|
|
572
576
|
|
|
573
|
-
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.
|
|
577
|
+
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.72 install`; o instalador detecta e reutiliza o que já estiver válido.
|
|
574
578
|
|
|
575
579
|
### Niche Finder Apify
|
|
576
580
|
|
|
@@ -622,9 +626,14 @@ Em **Configurações > Contas Google**, cada conta aparece como um expander iden
|
|
|
622
626
|
|
|
623
627
|
Em **Canais Youtube > Canais cadastrados**, a secção **Upload directo — documento da conta deste canal** mostra apenas a conta Google associada e confirma se o documento contém o ID delegado desse canal. O `DELEGATED_SESSION_ID` permanece exclusivamente no mapa do documento; o upload bloqueia a operação se faltar qualquer elemento técnico.
|
|
624
628
|
|
|
629
|
+
### API Bilibili — várias contas
|
|
630
|
+
A subaba **Configuração API > API Bilibili** fica entre **API Tiktok** e **AI Influencers**. Use **Adicionar nova API** para criar cards separados. Em cada card, preencha um nome, marque **Conta activa no Upload** quando a conta puder ser usada, introduza `SESSDATA`, `bili_jct` e `BUVID3`, e adicione `BUVID4`, `DedeUserID`, `ac_time_value` ou proxy apenas quando necessário. **Testar chamada API** valida a sessão sem criar upload; **Guardar card** persiste a conta e **Apagar card** remove-a. Os cookies são campos protegidos e não aparecem em mensagens, logs ou `uploads.json`.
|
|
631
|
+
|
|
632
|
+
Em **Upload > Upload convencional**, seleccione **Bilibili** em **Destinos**, escolha a conta activa e preencha os campos apresentados no card do vídeo. O botão **Enviar via bilibili-api (Python)** executa o upload do MP4/MOV/MKV/WEBM, gera uma capa de primeiro frame quando a thumbnail não existe e guarda o resultado localmente. O pacote Python é opcional no import, mas é instalado pelo requirements normal; se a dependência ou a sessão estiverem indisponíveis, o erro identifica a API sem bloquear as restantes redes.
|
|
633
|
+
|
|
625
634
|
### Upload-Post
|
|
626
635
|
|
|
627
|
-
A subaba **Upload-Post** usa a API oficial do [Upload-Post](https://docs.upload-post.com/) para publicar vídeos prontos em uma ou mais plataformas ligadas ao perfil configurado. Em **Configuração API > API Keys > Serviços e modelos > Publicação através do Upload-Post**, active a integração, guarde a API key, o username/perfil e as plataformas padrão. Depois abra **Upload > Upload-Post**, confirme ou altere as plataformas, escreva o título e a descrição e clique em **Enviar vídeo pelo Upload-Post**.
|
|
636
|
+
A subaba **Upload-Post** usa a API oficial do [Upload-Post](https://docs.upload-post.com/) para publicar vídeos prontos em uma ou mais plataformas ligadas ao perfil configurado. Em **Configuração API > API Keys > Serviços e modelos > Publicação através do Upload-Post**, active a integração, guarde a API key, o username/perfil e as plataformas padrão. **Plataformas Upload-Post** é um campo textual; escreva os slugs separados por vírgulas, por exemplo `youtube,tiktok`, exactamente como os destinos seleccionáveis em **Upload > Upload convencional**. Depois abra **Upload > Upload-Post**, confirme ou altere as plataformas, escreva o título e a descrição e clique em **Enviar vídeo pelo Upload-Post**.
|
|
628
637
|
|
|
629
638
|
A credencial não é duplicada na página de Upload. A integração envia o ficheiro local como `video` em `multipart/form-data`, usa `platform[]` repetido para cada destino e autentica com `Authorization: Apikey ...`. **Processar em segundo plano** envia `async_upload=true`; quando a API devolver `request_id`, este fica visível e é guardado em `uploads.json`. O histórico local também alimenta a notificação **Upload-Post concluído**. O Upload-Post é distinto do Postiz: não usa os endpoints nem a selecção de integrações do Postiz.
|
|
630
639
|
|
package/README.md
CHANGED
|
@@ -18,12 +18,12 @@ A primeira versão implementa a camada UI independente com:
|
|
|
18
18
|
| Brandings | Subaba própria dentro de Blueprints, upload/listagem de Brandings e criação conjunta com Blueprint |
|
|
19
19
|
| Canais Youtube | Subabas de importação pública sem API Key, Canais em lote gmail por conta Google/YouTube via OAuth e cadastro manual independente; cartões com botão Editar, nicho/referências visível, Prompts do Canal/Blueprint, Narrador/voz e gestão dos últimos 10 vídeos |
|
|
20
20
|
| Criação de Vídeos / Criação de Músicas | Subabas Criar vídeo e Vídeos; idiomas históricos preservados e dez códigos MoneyPrinterTurbo com bandeiras; 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 |
|
|
21
|
-
| Upload Música | Subabas **JewelMusic**, **Pushtunes** e **
|
|
21
|
+
| Upload Música | Subabas **JewelMusic**, **Pushtunes**, **ytmusicapi** e **DistroKid** para upload/sincronização musical com credenciais e histórico locais |
|
|
22
22
|
| Automação | Menu expansível com a subaba **Automação Youtube**, onde ficam os 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 |
|
|
23
|
-
| AI Influencers | Menu expansível com Personagens, Geração de Conteúdo IA,
|
|
23
|
+
| AI Influencers | Menu expansível com Personagens, Geração de Conteúdo IA, Motion Control, UGC Products e Redes Sociais; Personagens aceita múltiplas imagens e documentos `.md`/`.json`, e os dois workflows standalone guardam os resultados localmente sem Telegram ou publicação social |
|
|
24
24
|
| Niche Finder | Menu expansível com duas alternativas independentes: Niche Finder Kaggle e Niche Finder Apify, com parâmetros, execução e resultados separados |
|
|
25
25
|
| Edição | Menu expansível abaixo de Automação com Limpador de Metadados, Clip Generator local em Cortes e Editor Python inspirado no PYEdit para vídeos e scripts locais |
|
|
26
|
-
| Upload | YouTube via `youtube-automation-agent` adaptado internamente, fallback ordenado API Oficial → Upload directo → Postiz,
|
|
26
|
+
| Upload | YouTube via `youtube-automation-agent` adaptado internamente, fallback ordenado API Oficial → Upload directo → Postiz, Upload-Post textual e destinos locais Bilibili/TikTok/Instagram/Facebook Pages |
|
|
27
27
|
| MCP | Catálogo local opcional de Short Video Maker, AutoVio, OpenMontage e OpenCut, com portas editáveis e activação |
|
|
28
28
|
| Configurações | **Contas Google**, **Configuração API** e **Notificações**; Notificações contém as subabas **Geral** e **Telegram Gateway**, com envio das mesmas operações locais para um Chat ID; API Keys divididas entre **Serviços e modelos**, **API Tiktok** e **Fontes de materiais**, com várias chaves independentes por fonte, além de contas Google/YouTube por cartão, `INNERTUBE_API_KEY` global para todas as contas, providers, TTS, Nano Banana, Postiz, TikTok e Upload-Post |
|
|
29
29
|
| Launcher | Execução via `npx`, instalação assistida, diagnóstico e preparação para distribuição |
|
|
@@ -34,7 +34,11 @@ A área **AI Influencers > Personagens** permite guardar um personagem com nome,
|
|
|
34
34
|
|
|
35
35
|
O backend local predefinido é **SQLite**, por isso Personagens e Geração de Conteúdo IA funcionam imediatamente sem credenciais externas. Em **Configurações > Configuração API > AI Influencers**, o selector **Backend da base de dados de AI Influencers** aparece logo abaixo do estado do backend. O card **Supabase** contém apenas **Supabase Project URL** e **Supabase API key**. Se **Supabase** estiver seleccionado mas faltar qualquer uma dessas credenciais, o **Backend activo** muda automaticamente para **SQLite**; quando ambas estão preenchidas, passa para Supabase. O SQLite usa internamente `storage/state/ai_influencers.db` e `storage/influencers/`, sem campos editáveis de caminho ou Storage bucket. O botão **Testar ligação do backend** é read-only e não bloqueia a utilização local. A base SQLite fica na pasta persistente do Thunderbolt, fora da instalação temporária do pacote npm; durante uma actualização normal, personagens, assets e configurações são preservados. Se uma versão anterior tiver guardado `ai_influencers.db` no cache npm, o instalador procura uma cópia válida e recupera-a sem substituir uma base persistente já existente.
|
|
36
36
|
|
|
37
|
-
A página **AI Influencers > Geração de Conteúdo IA** contém as subabas **Imagens
|
|
37
|
+
A página **AI Influencers > Geração de Conteúdo IA** contém as subabas **Imagens** e **Vídeos**. Imagens usam os cartões activos do **Pool Imagem**; Vídeos usam os cartões activos do **Pool Vídeo** e exigem uma imagem inicial image-to-video. O selector de modelo não está preso ao Veo 3.1: pode utilizar KIE AI, Replicate, FAL AI, Pollinations ou outro cartão que declare suporte para vídeo. Para Replicate, o campo Modelo deve conter o identificador aceito pela API, como `owner/model` ou `owner/model:version`; a tarefa usa `POST /v1/predictions`, consulta o estado assíncrono e guarda o resultado localmente. A publicação para Instagram, TikTok, YouTube Shorts ou Facebook não é automática.
|
|
38
|
+
|
|
39
|
+
A página **AI Influencers > Motion Control** adapta apenas a criação do workflow Kling 2.6 Motion Control. O formulário recebe um vídeo original `.mp4`/`.mov` de 3–30 segundos, uma imagem `.jpg`/`.jpeg`/`.png` até 10 MB e um prompt opcional até 2500 caracteres. Os ficheiros são preservados no storage local, enviados temporariamente para o upload oficial KIE e usados em `POST /api/v1/jobs/createTask`; o estado é consultado em `/api/v1/jobs/recordInfo`, o MP4 é descarregado imediatamente e a tarefa é guardada no backend AI Influencers. Não existe callback público, Telegram, Postiz, Google Drive, upload ou publicação social.
|
|
40
|
+
|
|
41
|
+
A página **AI Influencers > UGC Products** adapta somente a criação do workflow Gemini/VEO3. Recebe uma imagem local do produto e o campo **Roteiro de vídeo**. Um roteiro dividido por `---` é respeitado directamente; caso contrário, o pool LLM estrutura dois prompts com regras de continuidade, preservação do produto, acções fisicamente possíveis e sem texto incorporado, legendas ou watermark. Cada segmento usa `POST /api/v1/veo/generate` com `veo3_fast`, `imageUrls`, `duration: 8` e `resolution: 720p`; o polling usa o endpoint oficial `/api/v1/veo/record-info`. O Thunderbolt descarrega os dois MP4, junta-os localmente com FFmpeg e persiste o artefacto final, sem Telegram, Postiz, Drive ou redes sociais.
|
|
38
42
|
|
|
39
43
|
A migração é baseada no workflow público [AI Agents A-Z — episódio 35](https://github.com/gyoridavid/ai_agents_az/tree/main/episode_35), mas o runtime do Thunderbolt não instala nem executa n8n. Os formulários, Data Tables, waits e subworkflows são substituídos pela UI Streamlit, pelo repositório dual Supabase/SQLite, pelos adapters multimédia e pelos logs/notificações locais. As API keys são sempre configuradas localmente e não devem ser colocadas nos JSONs do workflow ou no GitHub.
|
|
40
44
|
|
|
@@ -50,7 +54,7 @@ Na subaba **Configuração API > API Keys > Imagem e Video**, o selector **Provi
|
|
|
50
54
|
|
|
51
55
|
## API Tiktok — múltiplas aplicações TikTok
|
|
52
56
|
|
|
53
|
-
A subaba **Configuração API > API Tiktok** fica entre **Contas Google** e **
|
|
57
|
+
A subaba **Configuração API > API Tiktok** fica entre **Contas Google** e **API Bilibili**. Cada aplicação TikTok é apresentada num card separado e contém apenas **TikTok Client ID** e **TikTok Client Secret**, ambos guardados no storage local. O botão **Adicionar nova API** cria cards adicionais; cada card dispõe de **Guardar card**, **Testar chamada API** e **Apagar card**. A autorização OAuth, os scopes e os tokens de publicação continuam fora desta subaba, no fluxo próprio do TikTok for Developers.
|
|
54
58
|
|
|
55
59
|
As credenciais antigas dos campos únicos `tiktok_client_key` e `tiktok_client_secret` são migradas automaticamente para o primeiro card sem serem eliminadas. Para compatibilidade com integrações existentes, o adapter TikTok utiliza o primeiro card completo e mantém fallback para os campos legados; a UI não altera os campos pedidos nem mistura Client ID/Secret com tokens, redirect URI ou scopes.
|
|
56
60
|
|
|
@@ -58,19 +62,27 @@ As credenciais antigas dos campos únicos `tiktok_client_key` e `tiktok_client_s
|
|
|
58
62
|
|
|
59
63
|
A página **Upload** contém quatro subabas: **Upload convencional**, **Upload directo**, **Postiz** e **Upload-Post**. A quarta alternativa usa a API oficial do [Upload-Post](https://docs.upload-post.com/) para enviar um vídeo local para uma ou mais plataformas ligadas ao perfil configurado. Seleccione as plataformas na própria subaba, confirme o título e a descrição e clique em **Enviar vídeo pelo Upload-Post**.
|
|
60
64
|
|
|
61
|
-
A API key, o username/perfil e a lista inicial de plataformas continuam em **Configuração API > API Keys > Serviços e modelos**, no expander **Publicação através do Upload-Post**. A subaba não pede novamente a credencial. O cliente envia `multipart/form-data` para `https://api.upload-post.com/api/upload`, repete `platform[]` para cada destino e guarda a resposta, o `request_id` e o resultado no histórico local de uploads. A opção **Processar em segundo plano** usa `async_upload=true` quando a API estiver configurada para processamento assíncrono.
|
|
65
|
+
A API key, o username/perfil e a lista inicial de plataformas continuam em **Configuração API > API Keys > Serviços e modelos**, no expander **Publicação através do Upload-Post**. **Plataformas Upload-Post** é um campo textual: escreva os slugs separados por vírgulas, como `youtube,tiktok`, tal como os destinos da página Upload. A subaba não pede novamente a credencial. O cliente envia `multipart/form-data` para `https://api.upload-post.com/api/upload`, repete `platform[]` para cada destino e guarda a resposta, o `request_id` e o resultado no histórico local de uploads. A opção **Processar em segundo plano** usa `async_upload=true` quando a API estiver configurada para processamento assíncrono.
|
|
62
66
|
|
|
63
67
|
O Upload-Post é independente do Postiz: Postiz continua a usar o seu fluxo próprio de asset + post, enquanto Upload-Post publica directamente nas plataformas ligadas ao username configurado. Uma publicação aceite pelo Upload-Post também é reconciliada no centro de **Notificações**.
|
|
64
68
|
|
|
65
|
-
##
|
|
69
|
+
## Bilibili — upload via bilibili-api (Python)
|
|
70
|
+
A opção **Bilibili** está disponível em **Upload > Upload convencional > Destinos**. Seleccione uma conta Bilibili activa, reveja título, descrição, tags e ID da secção, e clique em **Enviar via bilibili-api (Python)**. O adapter usa `bilibili-api-python` 17.4.2, que expõe `Credential` com `SESSDATA`, `bili_jct` e `BUVID3` e executa o `VideoUploader` assíncrono a partir da UI síncrona. A capa usa a thumbnail do vídeo quando existe; caso contrário, é extraído localmente um primeiro frame com FFmpeg.
|
|
71
|
+
|
|
72
|
+
A subaba **Configuração API > API Bilibili**, colocada entre **API Tiktok** e **AI Influencers**, permite criar, editar, testar e apagar vários cards. Cada card guarda label, estado activo, `SESSDATA`, `bili_jct`, `BUVID3` e campos opcionais `BUVID4`, `DedeUserID`, `ac_time_value` e proxy. Os cookies são campos protegidos, não entram no histórico de uploads nem em logs, e o teste de chamada é read-only. O repositório upstream foi arquivado e encerrado em 2026; por isso a integração é opcional e os erros de disponibilidade do SDK são apresentados sem falhar o arranque do Thunderbolt.
|
|
66
73
|
|
|
67
|
-
|
|
74
|
+
## Upload Música — JewelMusic, Pushtunes, ytmusicapi e DistroKid
|
|
75
|
+
|
|
76
|
+
A página **Upload Música** contém quatro subabas independentes. **JewelMusic** usa a API documentada do [JewelMusic SDK](https://github.com/jewelmusic/sdk) para enviar tracks com metadados de título, artista, álbum, ano e género; a API key, a Base URL, o proxy opcional e o timeout ficam no storage local. O teste de ligação é read-only e não cria uma track.
|
|
68
77
|
|
|
69
78
|
**Pushtunes** usa o pacote [Pushtunes](https://pypi.org/project/pushtunes/) instalado pelo Thunderbolt para sincronizar tracks, álbuns ou playlists entre uma fonte Subsonic/Jellyfin/CSV/Spotify/YouTube Music e um destino Spotify/YouTube Music/Tidal/CSV. A UI aceita perfil `.toml`, CSV, `browser.json`, `tidal-session.json`, playlist, similaridade, directório de trabalho e credenciais Spotify; a execução chama o CLI sem shell e mascara segredos na saída. Pushtunes é uma sincronização de biblioteca, não um upload de bytes de um MP3 isolado.
|
|
70
79
|
|
|
71
80
|
**ytmusicapi** adapta o [ytmusicapi](https://github.com/sigma67/ytmusicapi) para upload directo de MP3, M4A, WMA, FLAC ou OGG para YouTube Music. O método exige um `browser.json` configurado pelo utilizador, que pode ser carregado na subaba e guardado em `storage/ytmusicapi/`; o teste consulta a biblioteca de uploads sem escrever. O histórico de cada operação é guardado localmente em `uploads.json` e não inclui o conteúdo dos segredos.
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
**DistroKid** adapta somente a etapa de upload do [musikai](https://github.com/igolaizola/musikai): recebe várias faixas e uma capa local, abre o formulário de novo lançamento no browser, preenche artista, título, record label, género e títulos das faixas, e carrega os ficheiros. O cookie de sessão é guardado localmente e nunca é mostrado nos resultados. A submissão final permanece manual no browser; o Thunderbolt não clica automaticamente em Submit. O browser pode usar o Chrome instalado ou um executável indicado na configuração.
|
|
83
|
+
|
|
84
|
+
As credenciais destes quatro métodos são opcionais e independentes das credenciais de vídeo. O instalador acrescenta `ytmusicapi`, `pushtunes`, `bilibili-api-python` e `playwright`; o adaptador JewelMusic usa o contrato HTTP documentado porque o SDK Python upstream não estava publicado no PyPI no momento da implementação. Consulte `THIRD-PARTY-NOTICES.md` antes de redistribuir o runtime.
|
|
85
|
+
|
|
74
86
|
|
|
75
87
|
## Upload directo — credenciais por conta e por canal
|
|
76
88
|
|
|
@@ -95,9 +107,9 @@ No topo da área principal da aplicação existe o menu nativo de idioma no padr
|
|
|
95
107
|
|
|
96
108
|
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.
|
|
97
109
|
|
|
98
|
-
## Navegação da UI 0.3.
|
|
110
|
+
## Navegação da UI 0.3.72
|
|
99
111
|
|
|
100
|
-
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
|
|
112
|
+
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**, **Motion Control**, **UGC Products**, **Redes Sociais**, **Tutorial Meta** e **Tutorial Supabase**, nessa ordem. A criação de Motion Control e UGC Products ocorre nas páginas standalone e guarda os artefactos localmente; não existem tabs duplicadas dentro de Geração de Conteúdo IA.
|
|
101
113
|
**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.
|
|
102
114
|
|
|
103
115
|
A subaba **Tutorial Supabase** apresenta o guia de criação das tabelas `plans` e `posts` e do bucket `instagram-images`, com ligação para a fonte original no GitHub. As duas frases promocionais da comunidade foram omitidas. Todas as subabas internas também são localizadas pelo helper nativo de tabs:
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -29,3 +29,12 @@ The JewelMusic upload adapter follows the documented HTTP contract and API examp
|
|
|
29
29
|
## supabase-py
|
|
30
30
|
|
|
31
31
|
Thunderbolt optionally depends on [supabase-py](https://github.com/supabase/supabase-py) for the AI Influencers Supabase backend. The package is distributed under the MIT License. The SQLite backend uses Python's standard library and does not require a remote service.
|
|
32
|
+
|
|
33
|
+
## bilibili-api-python
|
|
34
|
+
Thunderbolt depends optionally on [bilibili-api-python](https://pypi.org/project/bilibili-api-python/) for the Bilibili video-upload adapter. The package is distributed under the GNU General Public License v3 or later and its upstream repository [Nemo2011/bilibili-api](https://github.com/Nemo2011/bilibili-api) was archived and announced as closed on 2026-07-06. This dependency is separate from Thunderbolt's MIT License; consult the complete upstream licence and use the integration only with accounts and content you are authorised to operate.
|
|
35
|
+
|
|
36
|
+
## Playwright
|
|
37
|
+
Thunderbolt optionally depends on [Playwright for Python](https://playwright.dev/python/) to open the DistroKid upload form and load local tracks. The adapter does not submit the final form automatically. Playwright's own licence and browser distribution terms remain applicable.
|
|
38
|
+
|
|
39
|
+
## musikai
|
|
40
|
+
The DistroKid workflow is conceptually adapted from [igolaizola/musikai](https://github.com/igolaizola/musikai), an MIT-licensed Go project. Thunderbolt does not bundle musikai or its Go binaries; only the documented browser-assisted upload concept and metadata flow are adapted.
|
package/app/influencers_ui.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import base64
|
|
6
|
+
import hashlib
|
|
6
7
|
import io
|
|
7
8
|
import json
|
|
8
9
|
from pathlib import Path
|
|
@@ -13,14 +14,26 @@ import streamlit as st
|
|
|
13
14
|
from hermes_ui.influencers import (
|
|
14
15
|
BACKEND_OPTIONS,
|
|
15
16
|
InfluencerBackendError,
|
|
17
|
+
STANDALONE_CONTENT_INFLUENCER_ID,
|
|
16
18
|
backend_name,
|
|
17
19
|
backend_status,
|
|
20
|
+
ensure_standalone_content_owner,
|
|
18
21
|
get_repository,
|
|
19
22
|
test_backend,
|
|
20
23
|
)
|
|
21
|
-
from hermes_ui.media_generation import
|
|
24
|
+
from hermes_ui.media_generation import (
|
|
25
|
+
MediaGenerationError,
|
|
26
|
+
generate_image_for_card,
|
|
27
|
+
generate_motion_control_video,
|
|
28
|
+
generate_ugc_product_video,
|
|
29
|
+
generate_video_for_card,
|
|
30
|
+
upload_kie_file,
|
|
31
|
+
validate_motion_control_file,
|
|
32
|
+
)
|
|
33
|
+
from hermes_ui.creative_generation import generate_ugc_segment_prompts
|
|
22
34
|
from hermes_ui.media_providers import media_cards_for_pool, media_provider_definition
|
|
23
35
|
from hermes_ui.notifications import record_notification
|
|
36
|
+
from hermes_ui.storage import STORAGE, ensure_storage
|
|
24
37
|
|
|
25
38
|
|
|
26
39
|
CONTENT_STATES = {
|
|
@@ -195,7 +208,7 @@ def render_ai_influencer_characters(
|
|
|
195
208
|
|
|
196
209
|
with created_characters_tab:
|
|
197
210
|
try:
|
|
198
|
-
influencers = repository.list_influencers()
|
|
211
|
+
influencers = [item for item in repository.list_influencers() if str(item.get("id") or "") != STANDALONE_CONTENT_INFLUENCER_ID]
|
|
199
212
|
except Exception:
|
|
200
213
|
st.error("Não foi possível consultar os personagens no backend seleccionado.")
|
|
201
214
|
return
|
|
@@ -301,6 +314,170 @@ def _render_content_history(repository: Any, influencer_id: str = "") -> None:
|
|
|
301
314
|
st.video(str(artifact))
|
|
302
315
|
|
|
303
316
|
|
|
317
|
+
def _store_uploaded_file(uploaded: Any, folder: str) -> Path:
|
|
318
|
+
"""Persist an uploaded Streamlit file under storage without trusting its filename."""
|
|
319
|
+
ensure_storage()
|
|
320
|
+
original = Path(str(getattr(uploaded, "name", "upload.bin") or "upload.bin")).name
|
|
321
|
+
safe_name = "".join(char if char.isalnum() or char in ".-_" else "_" for char in original).strip("._") or "upload.bin"
|
|
322
|
+
content = uploaded.getvalue()
|
|
323
|
+
if not content:
|
|
324
|
+
raise MediaGenerationError(f"O ficheiro {safe_name} está vazio.")
|
|
325
|
+
digest = hashlib.sha256(content).hexdigest()[:16]
|
|
326
|
+
destination = STORAGE / "influencer_workflows" / folder / f"{digest}-{safe_name}"
|
|
327
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
328
|
+
if not destination.exists():
|
|
329
|
+
destination.write_bytes(content)
|
|
330
|
+
return destination
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _workflow_owner(repository: Any) -> str:
|
|
334
|
+
return ensure_standalone_content_owner(repository)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _workflow_provider_cards(settings: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
338
|
+
return [card for card in media_cards_for_pool(settings, "video") if str(card.get("provider") or "").strip().lower() == "kie_ai"]
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def render_motion_control(settings: dict[str, Any]) -> None:
|
|
342
|
+
"""Render Kling 2.6 Motion Control creation only; no social delivery is exposed."""
|
|
343
|
+
st.title("Motion Control")
|
|
344
|
+
st.caption("Crie um vídeo Kling 2.6 a partir de um vídeo de movimento e de uma imagem de referência. O resultado é descarregado para o storage local; não há Telegram, Postiz, Drive ou publicação social.")
|
|
345
|
+
repository = _repository(settings)
|
|
346
|
+
if repository is None:
|
|
347
|
+
return
|
|
348
|
+
cards = _workflow_provider_cards(settings)
|
|
349
|
+
if not cards:
|
|
350
|
+
st.warning("Active e configure pelo menos um cartão KIE AI no pool de vídeo em Configuração API > API Keys > Imagem e Video IA.")
|
|
351
|
+
return
|
|
352
|
+
card_options = [str(card.get("id") or "") for card in cards]
|
|
353
|
+
with st.form("influencer_motion_control_form"):
|
|
354
|
+
video_upload = st.file_uploader("Vídeo original de movimento", type=["mp4", "mov"], key="motion_control_video")
|
|
355
|
+
image_upload = st.file_uploader("Imagem de referência", type=["jpg", "jpeg", "png"], key="motion_control_image")
|
|
356
|
+
prompt = st.text_area("Prompt (opcional)", height=120, max_chars=2500, placeholder="Descreva como preservar a identidade da imagem e aplicar o movimento…", key="motion_control_prompt")
|
|
357
|
+
provider_id = st.selectbox("Provider / modelo", card_options, format_func=lambda value: _provider_label(next(card for card in cards if str(card.get("id")) == value)), key="motion_control_provider")
|
|
358
|
+
generate = st.form_submit_button("Gerar Motion Control", type="primary", use_container_width=True)
|
|
359
|
+
if generate:
|
|
360
|
+
if video_upload is None or image_upload is None:
|
|
361
|
+
st.error("Seleccione o vídeo original e a imagem de referência.")
|
|
362
|
+
return
|
|
363
|
+
card = next(card for card in cards if str(card.get("id")) == provider_id)
|
|
364
|
+
try:
|
|
365
|
+
video_path = _store_uploaded_file(video_upload, "motion-control-inputs")
|
|
366
|
+
image_path = _store_uploaded_file(image_upload, "motion-control-inputs")
|
|
367
|
+
video_info = validate_motion_control_file(video_path, kind="vídeo")
|
|
368
|
+
image_info = validate_motion_control_file(image_path, kind="imagem")
|
|
369
|
+
owner_id = _workflow_owner(repository)
|
|
370
|
+
record = repository.create_content(
|
|
371
|
+
{
|
|
372
|
+
"influencer_id": owner_id,
|
|
373
|
+
"content_type": "video",
|
|
374
|
+
"prompt": prompt,
|
|
375
|
+
"caption": "",
|
|
376
|
+
"provider": card.get("provider"),
|
|
377
|
+
"model": "kling-2.6/motion-control",
|
|
378
|
+
"platform": "",
|
|
379
|
+
"state": "running",
|
|
380
|
+
"metadata": {
|
|
381
|
+
"workflow": "motion_control",
|
|
382
|
+
"input_video_path": str(video_path),
|
|
383
|
+
"reference_image_path": str(image_path),
|
|
384
|
+
"input_video_duration_seconds": video_info.get("duration_seconds"),
|
|
385
|
+
"input_video_size_bytes": video_info.get("size_bytes"),
|
|
386
|
+
"reference_image_size_bytes": image_info.get("size_bytes"),
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
with st.spinner("A enviar os inputs para KIE e a aguardar o Kling Motion Control…"):
|
|
391
|
+
try:
|
|
392
|
+
image_url = upload_kie_file(image_path, card, upload_path="thunderbolt/motion-control")
|
|
393
|
+
video_url = upload_kie_file(video_path, card, upload_path="thunderbolt/motion-control")
|
|
394
|
+
output, task_id = generate_motion_control_video(settings, card, image_url=image_url, video_url=video_url, prompt=prompt)
|
|
395
|
+
metadata = {"workflow": "motion_control", "input_video_path": str(video_path), "reference_image_path": str(image_path), "kie_input_urls_temporary": True}
|
|
396
|
+
repository.update_content(record["id"], {"state": "completed", "artifact_path": str(output), "provider_request_id": task_id, "metadata": metadata})
|
|
397
|
+
record_notification("influencer_content_completed", "Motion Control concluído", "O vídeo Motion Control foi gerado e guardado localmente.", metadata={"content_id": record["id"], "workflow": "motion_control", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:completed")
|
|
398
|
+
st.success("Motion Control concluído. O vídeo está disponível no histórico local abaixo.")
|
|
399
|
+
except Exception as exc:
|
|
400
|
+
repository.update_content(record["id"], {"state": "failed", "error": str(exc)[:1000]})
|
|
401
|
+
record_notification("influencer_content_failed", "Motion Control falhou", f"A criação Motion Control falhou: {str(exc)[:240]}", metadata={"content_id": record["id"], "workflow": "motion_control", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:failed")
|
|
402
|
+
st.error(f"Não foi possível gerar o Motion Control: {exc}")
|
|
403
|
+
except (MediaGenerationError, ValueError, OSError) as exc:
|
|
404
|
+
st.error(str(exc))
|
|
405
|
+
_render_content_history(repository, STANDALONE_CONTENT_INFLUENCER_ID)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def render_ugc_products(settings: dict[str, Any]) -> None:
|
|
409
|
+
"""Render the local product-image to VEO3 workflow without Telegram or social publishing."""
|
|
410
|
+
st.title("UGC Products")
|
|
411
|
+
st.caption("Crie um anúncio UGC a partir de uma imagem de produto e de um roteiro local. O workflow gera dois clips VEO3 de 8 segundos e junta-os com FFmpeg no storage local.")
|
|
412
|
+
repository = _repository(settings)
|
|
413
|
+
if repository is None:
|
|
414
|
+
return
|
|
415
|
+
video_cards = [card for card in _workflow_provider_cards(settings) if str(card.get("model") or "").strip().lower() in {"", "veo3", "veo3_fast", "veo3_lite"}]
|
|
416
|
+
if not video_cards:
|
|
417
|
+
st.warning("Active e configure um cartão KIE AI com modelo VEO3 compatível no pool de vídeo em Configuração API > API Keys > Imagem e Video IA.")
|
|
418
|
+
return
|
|
419
|
+
image_cards, _ = _provider_options(settings, "image")
|
|
420
|
+
image_cards = [card for card in image_cards if str(card.get("provider") or "").strip().lower() == "nano_banana"]
|
|
421
|
+
with st.form("influencer_ugc_products_form"):
|
|
422
|
+
product_upload = st.file_uploader("Imagem do produto", type=["jpg", "jpeg", "png", "webp"], key="ugc_products_image")
|
|
423
|
+
script = st.text_area("Roteiro de vídeo", height=180, placeholder="Escreva a demonstração, falas e acções. Para controlar os dois clips manualmente, separe-os com uma linha contendo ---.", key="ugc_products_script")
|
|
424
|
+
improve_image = st.checkbox("Melhorar a imagem com o provider de imagem activo (opcional)", value=False, key="ugc_products_improve_image")
|
|
425
|
+
image_provider_id = ""
|
|
426
|
+
if improve_image and image_cards:
|
|
427
|
+
image_provider_options = [str(card.get("id") or "") for card in image_cards]
|
|
428
|
+
image_provider_id = st.selectbox("Provider de melhoria da imagem", image_provider_options, format_func=lambda value: _provider_label(next(card for card in image_cards if str(card.get("id")) == value)), key="ugc_products_image_provider")
|
|
429
|
+
elif improve_image:
|
|
430
|
+
st.info("Não existe um provider activo no pool de imagem; será usada a imagem original.")
|
|
431
|
+
card_options = [str(card.get("id") or "") for card in video_cards]
|
|
432
|
+
provider_id = st.selectbox("Provider VEO3", card_options, format_func=lambda value: _provider_label(next(card for card in video_cards if str(card.get("id")) == value)), key="ugc_products_video_provider")
|
|
433
|
+
generate = st.form_submit_button("Gerar UGC Product", type="primary", use_container_width=True)
|
|
434
|
+
if generate:
|
|
435
|
+
if product_upload is None:
|
|
436
|
+
st.error("Seleccione a imagem do produto.")
|
|
437
|
+
return
|
|
438
|
+
card = next(card for card in video_cards if str(card.get("id")) == provider_id)
|
|
439
|
+
try:
|
|
440
|
+
product_path = _store_uploaded_file(product_upload, "ugc-products-inputs")
|
|
441
|
+
image_path = product_path
|
|
442
|
+
if improve_image and image_provider_id:
|
|
443
|
+
image_card = next(card for card in image_cards if str(card.get("id")) == image_provider_id)
|
|
444
|
+
with st.spinner("A melhorar a imagem do produto…"):
|
|
445
|
+
image_path = generate_image_for_card(settings, image_card, "Melhorar a apresentação fotográfica do mesmo produto sem alterar a embalagem, logótipo, cores, forma ou objectos visíveis. Fundo comercial limpo e luz natural.", topic="UGC Product", reference_image=product_path)
|
|
446
|
+
prompts = generate_ugc_segment_prompts(settings, script)
|
|
447
|
+
owner_id = _workflow_owner(repository)
|
|
448
|
+
record = repository.create_content(
|
|
449
|
+
{
|
|
450
|
+
"influencer_id": owner_id,
|
|
451
|
+
"content_type": "video",
|
|
452
|
+
"prompt": script,
|
|
453
|
+
"caption": "",
|
|
454
|
+
"provider": card.get("provider"),
|
|
455
|
+
"model": str(card.get("model") or "veo3_fast"),
|
|
456
|
+
"platform": "",
|
|
457
|
+
"state": "running",
|
|
458
|
+
"metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "telegram": False, "social_publish": False},
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
with st.spinner("A enviar a imagem para KIE, a gerar os dois clips VEO3 e a juntá-los localmente…"):
|
|
462
|
+
try:
|
|
463
|
+
image_url = upload_kie_file(image_path, card, upload_path="thunderbolt/ugc-products")
|
|
464
|
+
output, task_ids = generate_ugc_product_video(settings, card, image_url=image_url, prompts=prompts)
|
|
465
|
+
repository.update_content(record["id"], {"state": "completed", "artifact_path": str(output), "provider_request_id": ",".join(task_ids), "metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "task_ids": task_ids, "telegram": False, "social_publish": False}})
|
|
466
|
+
record_notification("influencer_content_completed", "UGC Products concluído", "O vídeo UGC Product foi gerado, unido e guardado localmente.", metadata={"content_id": record["id"], "workflow": "ugc_products", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:completed")
|
|
467
|
+
st.success("UGC Product concluído. O vídeo final está disponível no histórico local abaixo.")
|
|
468
|
+
with st.expander("Prompts dos dois segmentos", expanded=False):
|
|
469
|
+
for index, prompt_item in enumerate(prompts, start=1):
|
|
470
|
+
st.markdown(f"**Segmento {index}**")
|
|
471
|
+
st.code(prompt_item)
|
|
472
|
+
except Exception as exc:
|
|
473
|
+
repository.update_content(record["id"], {"state": "failed", "error": str(exc)[:1000]})
|
|
474
|
+
record_notification("influencer_content_failed", "UGC Products falhou", f"A criação UGC Products falhou: {str(exc)[:240]}", metadata={"content_id": record["id"], "workflow": "ugc_products", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:failed")
|
|
475
|
+
st.error(f"Não foi possível gerar o UGC Product: {exc}")
|
|
476
|
+
except (MediaGenerationError, ValueError, OSError) as exc:
|
|
477
|
+
st.error(str(exc))
|
|
478
|
+
_render_content_history(repository, STANDALONE_CONTENT_INFLUENCER_ID)
|
|
479
|
+
|
|
480
|
+
|
|
304
481
|
def render_ai_influencer_content(settings: dict[str, Any]) -> None:
|
|
305
482
|
st.title("Geração de Conteúdo IA")
|
|
306
483
|
st.caption("Gere conteúdos para redes sociais a partir de um personagem e dos seus assets. A publicação permanece separada e exige uma acção explícita.")
|
|
@@ -308,11 +485,11 @@ def render_ai_influencer_content(settings: dict[str, Any]) -> None:
|
|
|
308
485
|
if repository is None:
|
|
309
486
|
return
|
|
310
487
|
try:
|
|
311
|
-
influencers = repository.list_influencers()
|
|
488
|
+
influencers = [item for item in repository.list_influencers() if str(item.get("id") or "") != STANDALONE_CONTENT_INFLUENCER_ID]
|
|
312
489
|
except Exception:
|
|
313
490
|
st.error("Não foi possível consultar os personagens.")
|
|
314
491
|
return
|
|
315
|
-
images_tab, videos_tab
|
|
492
|
+
images_tab, videos_tab = st.tabs(["Imagens", "Vídeos"])
|
|
316
493
|
|
|
317
494
|
with images_tab:
|
|
318
495
|
st.subheader("Gerar imagem para redes sociais")
|
|
@@ -415,13 +592,11 @@ def render_ai_influencer_content(settings: dict[str, Any]) -> None:
|
|
|
415
592
|
selected_id = str(st.session_state.get("content_video_influencer") or options[0])
|
|
416
593
|
_render_content_history(repository, selected_id)
|
|
417
594
|
|
|
418
|
-
with motion_tab:
|
|
419
|
-
st.subheader("Motion Control")
|
|
420
|
-
st.info("A subaba Motion Control foi preparada para a próxima evolução. Nenhuma operação é executada nesta versão.")
|
|
421
|
-
|
|
422
595
|
|
|
423
596
|
__all__ = [
|
|
424
597
|
"render_ai_influencer_characters",
|
|
425
598
|
"render_ai_influencer_content",
|
|
599
|
+
"render_motion_control",
|
|
600
|
+
"render_ugc_products",
|
|
426
601
|
"render_ai_influencers_api_status",
|
|
427
602
|
]
|