primocode 10.10.0 → 10.10.1
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/README.md +2 -2
- package/lib/catalogo.js +6 -3
- package/lib/comandos-extra.js +2 -1
- package/lib/entrada.js +18 -0
- package/lib/estudio/cena.js +47 -0
- package/lib/estudio/estilos.js +147 -0
- package/lib/estudio/gerativo.js +19 -3
- package/lib/estudio/index.js +57 -11
- package/lib/tools.js +11 -0
- package/package.json +2 -2
- package/skills/motion-de-primeira/SKILL.md +35 -36
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.10.
|
|
1
|
+
# PrimoCode v10.10.1
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v10.10.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.10.1
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/catalogo.js
CHANGED
|
@@ -93,8 +93,8 @@ const GRUPOS = [
|
|
|
93
93
|
+ 'contraste{nao,sim} citacao{texto,autor} imagem{busca,titulo,sub} grafico{titulo,barras:[{rotulo,valor}]} cta{titulo,link} '
|
|
94
94
|
+ 'logo{titulo} cores{cores:[{nome,hex}]} produto{titulo,texto,destaque,selo,barras}. Em qualquer parte: destaque (a palavra-tese), fonte (a fonte citada). O texto da TELA sai da FALA: o título usa palavras que a fala diz, cedo na frase (a tela entra quando a voz diz; palavra que a voz não diz deixa a tela esperando). '
|
|
95
95
|
+ 'visual:{titulo,texto,paleta} (hibrido, cinema, impacto): fonte e cor do cardápio (estudio_midia acao:fontes); sem visual, o PrimoCode sorteia sem repetir os últimos vídeos. '
|
|
96
|
-
+ 'componente:"componentes/x.jsx" numa parte = a cena que VOCÊ escreveu em React (a voz, a trilha
|
|
97
|
-
+ 'estilo (deixe VAZIO: o
|
|
96
|
+
+ 'componente:"componentes/x.jsx" numa parte = a cena que VOCÊ escreveu em React — é o caminho do motion (skill Motion de primeira): uma ideia desenhada por fala; a voz, a trilha, os sons e o fundo seguem do Estúdio; cena que não compila volta em cenasQuebradas. '
|
|
97
|
+
+ 'estilo (deixe VAZIO: o gerativo, com as suas cenas em componente e o estiloVisual que você montou; outro só se a pessoa pedir pelo nome): hibrido (SÓ quando a pessoa pediu imagem: foto e vídeo reais + o motion por cima; dê busca:"termo em inglês" só nas partes em que a imagem agrega — a COISA que a frase nomeia, 2 a 4 palavras, não a frase inteira; rota:"tipografia" em número/citação, "produto" quando a frase fala do produto; ancora:"palavra" — o clipe troca 0,2 s depois dela) | gerativo (= motion: sem template — a tipografia anima nos eixos da fonte numa linha do tempo contínua, composição, fonte, cor e som — estiloVisual {humor, paleta, display, texto, mono, genero, ambiente, acento, material} é o estilo que VOCÊ monta pelo assunto (cardápio: estudio_midia acao:estilos; vazio, o PrimoCode sorteia); modelo3d fone|celular|notebook|relogio|garrafa|lata|caixa: o objeto gira em 3D numa batida — ponha quando o vídeo é de um produto ou objeto físico; ideia abstrata, história ou explicação: deixe vazio; o vídeo tem UMA identidade (2 chãos, 1 acento, 2 fontes) e o fundo só troca na virada de seção — se o roteiro tem atos, dê secao:"nome curto" nas partes, senão o PrimoCode divide; moldura cantos|completa: o quadro fixo do reel (cantos, o assunto no alto, "01 · seção", o tempo) — SÓ quando a pessoa pedir; vazio = sem moldura (ela entrega que o vídeo foi gerado); heroi em cada parte: o desenho da batida além do texto, tirado do CONTEÚDO — um ícone 3D RENDERIZADO da biblioteca ("fluent3d:hot beverage", "fluent3d:money bag"; procure com estudio_midia acao:icones3d — é o que mais parece 3D de verdade), uma animação pronta ("noto:1f525"; acao:animacoes), um ícone do Iconify do objeto que a fala cita ("mdi:bread-slice", "mdi:qrcode"; acao:icones), um 3D do kit (raio|relogio|check|balao|escudo|coracao|estrela|sol|play|engrenagem|cadeado|barras|fone|onda|celular|seta|bateria) ou "nenhum" quando a tipografia basta; forma abstrata (explosao|poligono|esfera|toro|truchet|aneis|grade|rastro) só em assunto abstrato ou de tecnologia — numa conversa de padaria, nunca; ambiente limpo|luz|bokeh e acento traco|viagem (o ponto que viaja entre as batidas): vazio, o PrimoCode escolhe com memória e o bokeh quase não sai fora de tecnologia) | classico (o motion 3D antigo) | vox (explainer editorial: tinta sobre papel, câmera que conta a frase) | '
|
|
98
98
|
+ 'marca (brand reveal: brilho, letras em vetor, marca cromada, padrão) | lancamento (produto: cartões, painel, calendário) | fluido (minimalista: uma ideia por batida, a forma de uma vira a próxima, sem corte; paleta noite|breu|preto|cobalto (escuras) ou lavanda|creme|laranja|branco (claras) — escolha pelo assunto e NÃO repita a do vídeo anterior; vazio, o PrimoCode escolhe; fontesFluido aprovado|editorial|revista|carater|tecnico|classico, vazio gira) | impacto | cinema | personagem (a ANIMAÇÃO 3D de verdade, o vídeo GRAVADO em 3D no visual de blocos/voxel de render de cinema — é a escolha para animação 3D, personagem, bicho, boneco, avatar, mascote, podcast, entrevista ou alguém FALANDO em 3D; cada parte é UMA fala {falante, texto, emocao?, destaque?, acao?, cenario?}, sem tipo). NADA É FIXO, monte o que a pessoa pediu: cena:"texto livre do lugar" (praia, cidade, floresta, parque, fazenda, neve, deserto, espaço, fundo do mar, escritório, sala, cozinha, escola, palco… + hora: "à noite", "ao pôr do sol") ou cena:{lugar, hora, elementos:["palmeira","barraca","violão",…]} — elemento fora do catálogo vira ícone 3D em blocos; parte.cenario muda o lugar a partir daquela fala. Até 4 falantes; personagens:{"nome":{tipo:pessoa|robo|animal|coisa, especie (animal: cachorro|gato|cavalo|vaca|porco|ovelha|raposa|lobo|urso|panda|coelho|leao|tigre|elefante|girafa|tartaruga|galinha|pato|passaro|papagaio|coruja|pinguim|peixe|tubarao|golfinho|dinossauro|sapo|macaco), icone (coisa: "pizza", "café", "polvo" — qualquer objeto/bicho que fala), pessoa: pele, cabelo:curto|cacheado|longo|topete|coque|careca, corCabelo, roupa, oculos, barba; robo: cor}} — sem tipo, o nome decide (nome de bicho vira bicho, de IA vira o mascote laranja, de objeto vira coisa); figurantes:["cachorro","pessoa"] (quem está na cena sem falar); BICHO NÃO FALA: a fala de um bicho sai da narração e ele fica na cena — só fala se a pessoa pedir ("cachorro falante") ou personagens.x.fala:true; a voz de cada falante sai do GÊNERO do nome (personagens.x.genero:f|m manda), uma voz por personagem do começo ao fim; bigode:true e cabeca:"cubo" (o rosto de pixel antigo) em personagens; sem lugar no pedido, o set é a cobertura à noite (janela da cidade, cartões de interface na frente). acao em cada parte: acenar|apontar|pular|dancar|comemorar|pensar|andar|correr. emocao feliz|serio|surpreso|pensativo|animado|curioso; destaque:"texto curto"; paineis:["título",…] só no set de telas (o padrão quando o pedido não diz lugar); visual:"liso" só se pedirem o boneco liso antigo). '
|
|
99
99
|
+ 'marca e lancamento aceitam visual:orbe|vetor|esfera|grade|site|padrao|cromo|cartoes|painel|cores numa parte. marca.nome dá o nome. '
|
|
100
100
|
+ 'voz (thalita|antonio|francisca). DUAS VOZES (o pedido fala em dois narradores, diálogo, conversa, entrevista): roteiro.vozes {"nome":"antonio","outro":"thalita"} e falante:"nome" em cada parte — cada um sai na sua voz. Ajuste fino: plano: {formato:{largura,altura,fps}, cenas:[…], audio:{…}} — ou cenas direto. '
|
|
@@ -110,8 +110,11 @@ const GRUPOS = [
|
|
|
110
110
|
+ 'o render empurra para dentro, encolhe o texto largo e afasta o colado; enfeite de fundo marque decorativo: true.' },
|
|
111
111
|
|
|
112
112
|
{ tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
|
|
113
|
-
args: 'acao: biblioteca | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
113
|
+
args: 'acao: biblioteca | estilos | salvar_estilo | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
114
114
|
+ 'movimentos termo:"quique" categoria:entrada|saida|enfase|loop: os 100+ movimentos prontos com nome; no elemento, movimento:"<nome>" (ou {nome, em, duracao}, ou uma lista) vira quadros-chave sozinho. Varie: não use o mesmo em todo elemento. Pode inventar o seu em anima. '
|
|
115
|
+
+ 'estilos: as peças do gerativo (humor, paleta, fontes, trilha, ambiente, acento, material) — dezenas de milhares de combinações, e os estilos que a pessoa salvou. '
|
|
116
|
+
+ 'Monte o do vídeo pelo ASSUNTO em roteiro.estiloVisual; misture um salvo com {base:"<nome>", ...}; crie paleta nova com 5 cores. '
|
|
117
|
+
+ 'No fim, pergunte "posso salvar este estilo pra usar nos próximos vídeos?" e, SÓ com o sim, salvar_estilo com nome, pecas {…} e autorizado:true. '
|
|
115
118
|
+ 'cenarios: os lugares prontos do vídeo 3D e os que a pessoa já salvou. Pode CRIAR um cenário novo na cena (lugar base + ceu, chao, parede, luz, acento, hora, elementos); '
|
|
116
119
|
+ 'no fim do vídeo pergunte "posso salvar este cenário pra usar na próxima vez?" e, SÓ com o sim, salvar_cenario com nome, cenario {…} e autorizado:true. Depois, lugar:"<nome>" traz ele de volta. '
|
|
117
120
|
+ 'biblioteca: o cardápio INTEIRO do que existe (ícone 2D, ícone 3D, animação pronta, efeito sonoro, música, fonte, personagem 3D) — olhe antes de escolher. '
|
package/lib/comandos-extra.js
CHANGED
|
@@ -466,7 +466,8 @@ const ATALHOS = [
|
|
|
466
466
|
['Ctrl+C', 'limpa o que foi digitado; com a caixa vazia, sai'],
|
|
467
467
|
['Ctrl+D', 'sai (com a caixa vazia)'],
|
|
468
468
|
['Ctrl+L', 'limpa a tela'],
|
|
469
|
-
['
|
|
469
|
+
['↑ ↓ · roda · PgUp PgDn', 'rolam a conversa (na tela cheia)'],
|
|
470
|
+
['Option+↑ · Option+↓', 'o pedido anterior · o próximo (na tela cheia)'],
|
|
470
471
|
['arrastar o mouse', 'seleciona texto; Cmd+C (Mac) ou Ctrl+Shift+C copia'],
|
|
471
472
|
['Ctrl+A · Ctrl+E', 'vai para o começo · o fim da linha'],
|
|
472
473
|
['Ctrl+U · Ctrl+K', 'apaga antes · depois do cursor'],
|
package/lib/entrada.js
CHANGED
|
@@ -587,6 +587,20 @@ class Entrada extends EventEmitter {
|
|
|
587
587
|
this.desenhado = { linhas: q.linhas, cursorLinha: q.cursor.linha };
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
+
/* A RODA QUE VIRAVA HISTÓRICO (13 e 14/10: "quando eu tento subir o
|
|
591
|
+
histórico ele fica mostrando meus prompts"). Sem capturar o mouse (por
|
|
592
|
+
causa da seleção de texto), o terminal manda a roda como setas ↑↓. O
|
|
593
|
+
ritmo não separa as duas coisas: medido no Orca (14/10), a roda devagar
|
|
594
|
+
chega uma seta a cada 50–250 ms, e o ↑ segurado no teclado a cada 83 ms.
|
|
595
|
+
Então, na tela cheia, a seta ROLA a conversa (2 linhas), sempre; o
|
|
596
|
+
pedido anterior fica no Option+↑/↓ (ou Ctrl/Shift, o que o terminal mandar). */
|
|
597
|
+
_setaNaCheia(dir, key) {
|
|
598
|
+
// ESC ESC [A é o Option dos terminais com "Option como Meta": o Node entrega como seta pura.
|
|
599
|
+
const comOption = key && (key.meta || key.ctrl || key.shift || String(key.sequence || '').startsWith('\x1b\x1b'));
|
|
600
|
+
if (comOption) return this._historico(dir > 0 ? -1 : 1);
|
|
601
|
+
return this._rolar(dir * 2);
|
|
602
|
+
}
|
|
603
|
+
|
|
590
604
|
_rolar(passo) {
|
|
591
605
|
this.rolagem = Math.max(0, this.rolagem + passo);
|
|
592
606
|
this._pintar();
|
|
@@ -878,12 +892,14 @@ class Entrada extends EventEmitter {
|
|
|
878
892
|
|
|
879
893
|
case 'up':
|
|
880
894
|
if (this.menu) { this.menu.sel = Math.max(0, this.menu.sel - 1); return this._redesenhar(); }
|
|
895
|
+
if (this.cheia) return this._setaNaCheia(1, key);
|
|
881
896
|
return this._historico(-1);
|
|
882
897
|
case 'down':
|
|
883
898
|
if (this.menu) {
|
|
884
899
|
this.menu.sel = Math.min(this.menu.itens.length - 1, this.menu.sel + 1);
|
|
885
900
|
return this._redesenhar();
|
|
886
901
|
}
|
|
902
|
+
if (this.cheia) return this._setaNaCheia(-1, key);
|
|
887
903
|
return this._historico(1);
|
|
888
904
|
|
|
889
905
|
default:
|
|
@@ -893,6 +909,8 @@ class Entrada extends EventEmitter {
|
|
|
893
909
|
|
|
894
910
|
_ctrl(key) {
|
|
895
911
|
switch (key.name) {
|
|
912
|
+
case 'up': return this.cheia ? this._setaNaCheia(1, key) : this._historico(-1);
|
|
913
|
+
case 'down': return this.cheia ? this._setaNaCheia(-1, key) : this._historico(1);
|
|
896
914
|
case 'c':
|
|
897
915
|
// Com texto no campo, Ctrl+C limpa o que foi digitado — sair
|
|
898
916
|
// por acidente no meio de um pedido longo é perder o pedido.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cena.js — confere a cena que a IA escreveu antes do render.
|
|
3
|
+
*
|
|
4
|
+
* "só quero motions nesse nível" (Joel, 14/10, com as referências: cada
|
|
5
|
+
* cena é uma ideia desenhada para a fala, escrita pelo modelo).
|
|
6
|
+
*
|
|
7
|
+
* O caminho do motion é a IA escrever cada cena em React (parte.componente).
|
|
8
|
+
* Um erro de sintaxe num .jsx derrubava o bundle do Remotion e o vídeo
|
|
9
|
+
* inteiro. Aqui a cena é compilada (o esbuild que já mora no remotion-base)
|
|
10
|
+
* e os imports são conferidos: a cena quebrada sai, a parte cai no gerativo,
|
|
11
|
+
* e o erro volta para a IA consertar e mandar de novo.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const PERMITIDOS = /^(react|remotion|three|@remotion\/[\w-]+|\.{1,2}\/.+)$/;
|
|
21
|
+
|
|
22
|
+
let esbuild;
|
|
23
|
+
function compilador() {
|
|
24
|
+
if (esbuild !== undefined) return esbuild;
|
|
25
|
+
try { esbuild = require(path.join(os.homedir(), '.primocode', 'studio', 'remotion-base', 'node_modules', 'esbuild')); } catch { esbuild = null; }
|
|
26
|
+
return esbuild;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** {ok} ou {ok:false, erro} — o erro em uma linha, com a linha do arquivo. */
|
|
30
|
+
function conferirCena(arquivo, { compilar = compilador() } = {}) {
|
|
31
|
+
let codigo;
|
|
32
|
+
try { codigo = fs.readFileSync(arquivo, 'utf8'); } catch (e) { return { ok: false, erro: `não li ${path.basename(arquivo)}: ${e.message}` }; }
|
|
33
|
+
if (!/export\s+default\b/.test(codigo)) return { ok: false, erro: 'falta o export default da função da cena ({el, t, fps}).' };
|
|
34
|
+
const fora = [...codigo.matchAll(/\bfrom\s+['"]([^'"]+)['"]|\brequire\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1] || m[2]).filter((x) => !PERMITIDOS.test(x));
|
|
35
|
+
if (fora.length) return { ok: false, erro: `import fora do permitido: ${[...new Set(fora)].join(', ')} (só react, remotion, @remotion/*, three).` };
|
|
36
|
+
if (!compilar) return { ok: true, semCompilador: true };
|
|
37
|
+
try {
|
|
38
|
+
compilar.transformSync(codigo, { loader: /\.tsx$/i.test(arquivo) ? 'tsx' : 'jsx', jsx: 'automatic', logLevel: 'silent' });
|
|
39
|
+
return { ok: true };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
const x = (e.errors && e.errors[0]) || {};
|
|
42
|
+
const onde = x.location ? `linha ${x.location.line}: ` : '';
|
|
43
|
+
return { ok: false, erro: `${onde}${x.text || String(e.message).split('\n')[0]}` };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { conferirCena, PERMITIDOS };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* estilos.js — os milhares de estilos do gerativo, montados por peça.
|
|
3
|
+
*
|
|
4
|
+
* "volta pro remotion, terão 10 mil estilos e a IA pode misturar eles e
|
|
5
|
+
* criar novos." (Joel, 14/10)
|
|
6
|
+
*
|
|
7
|
+
* Um estilo é uma combinação de peças: humor, paleta (o sistema de 5 campos),
|
|
8
|
+
* fonte de título, de texto e mono, gênero da trilha, ambiente, acento e o
|
|
9
|
+
* material dos ícones. As peças são as do gerativo.js, então a conta sai do
|
|
10
|
+
* código (total()), e não de uma lista escrita à mão.
|
|
11
|
+
*
|
|
12
|
+
* Quem escolhe é a IA: ela pede o cardápio (estudio_midia acao:"estilos"),
|
|
13
|
+
* monta o estilo pelo assunto e passa roteiro.estiloVisual. O que ela deixar
|
|
14
|
+
* vazio, o sorteio com memória completa (escolherVisual). Ela também pode
|
|
15
|
+
* misturar um estilo salvo (base:"<nome>") e criar uma paleta nova (5 cores).
|
|
16
|
+
* O estilo que ficou bom só é guardado com o sim da pessoa, como os cenários.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const gerativo = require('./gerativo.js');
|
|
25
|
+
const { chave } = require('./cenarios.js');
|
|
26
|
+
|
|
27
|
+
const arquivo = () => path.join(os.homedir(), '.primocode', 'estilos.json');
|
|
28
|
+
const ehCor = (c) => typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
|
|
29
|
+
const unicos = (campo) => [...new Set(Object.values(gerativo.HUMORES).flatMap((h) => h[campo]))];
|
|
30
|
+
|
|
31
|
+
/** As paletas com nome: "comida-1" é o primeiro sistema de 5 campos do humor comida. */
|
|
32
|
+
function paletas() {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const [h, lista] of Object.entries(gerativo.CAMPOS)) lista.forEach((k, i) => { out[`${h}-${i + 1}`] = k; });
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pecas() {
|
|
39
|
+
return {
|
|
40
|
+
humor: Object.keys(gerativo.HUMORES),
|
|
41
|
+
paleta: Object.keys(paletas()),
|
|
42
|
+
display: unicos('displays').filter((f) => !gerativo.BANIDAS.includes(f) && gerativo.VARIAVEIS[f]),
|
|
43
|
+
texto: unicos('textos'),
|
|
44
|
+
mono: unicos('monos'),
|
|
45
|
+
genero: unicos('generos'),
|
|
46
|
+
ambiente: gerativo.AMBIENTES,
|
|
47
|
+
acento: gerativo.ACENTOS_JEITO,
|
|
48
|
+
material: gerativo.MATERIAIS_ICONE,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Quantos estilos dá pra montar só com as peças prontas (sem contar as paletas que a IA cria). */
|
|
53
|
+
function total() {
|
|
54
|
+
return Object.values(pecas()).reduce((n, lista) => n * lista.length, 1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Confere as peças que a IA escolheu. Peça que não existe vira aviso, com as
|
|
59
|
+
* opções, e não entra (o sorteio cobre). A paleta pode ser um nome ou 5 cores.
|
|
60
|
+
*/
|
|
61
|
+
function validar(pedido) {
|
|
62
|
+
const P = pecas(), out = {}, avisos = [];
|
|
63
|
+
for (const [campo, v] of Object.entries(pedido || {})) {
|
|
64
|
+
if (campo === 'base' || v == null || v === '') continue;
|
|
65
|
+
if (campo === 'paleta') {
|
|
66
|
+
if (Array.isArray(v)) {
|
|
67
|
+
if (v.length !== 5 || !v.every(ehCor)) avisos.push('paleta nova precisa de 5 cores #rrggbb: escura, assinatura, papel e duas ousadas.');
|
|
68
|
+
else if (v.some(gerativo.corGenerica)) avisos.push(`paleta ${v.join(' ')}: tem cor de cara de IA (violeta de SaaS ou neon); troque.`);
|
|
69
|
+
else out.paleta = v.map((c) => c.toUpperCase());
|
|
70
|
+
} else if (paletas()[v]) out.paleta = paletas()[v];
|
|
71
|
+
else avisos.push(`paleta "${v}" não existe: ${P.paleta.join(', ')} ou 5 cores suas.`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!P[campo]) { avisos.push(`"${campo}" não é peça de estilo: ${Object.keys(P).join(', ')}.`); continue; }
|
|
75
|
+
if (P[campo].includes(v)) out[campo] = v;
|
|
76
|
+
else avisos.push(`${campo} "${v}" não existe: ${P[campo].join(', ')}.`);
|
|
77
|
+
}
|
|
78
|
+
return { pecas: out, avisos };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* O estilo do vídeo a partir do que a IA pediu (objeto, ou o nome de um salvo).
|
|
83
|
+
* Devolve as escolhasGerativo prontas: o que ela não disse, o sorteio completa.
|
|
84
|
+
*/
|
|
85
|
+
function montar(pedido, roteiro = {}, { aleatorio } = {}) {
|
|
86
|
+
let p = typeof pedido === 'string' ? { base: pedido } : { ...(pedido || {}) };
|
|
87
|
+
const avisos = [];
|
|
88
|
+
if (p.base) {
|
|
89
|
+
const salvo = ler(p.base);
|
|
90
|
+
if (salvo) p = { ...salvo.pecas, ...p };
|
|
91
|
+
else avisos.push(`não existe estilo salvo "${p.base}".`);
|
|
92
|
+
}
|
|
93
|
+
const v = validar(p);
|
|
94
|
+
avisos.push(...v.avisos);
|
|
95
|
+
const humor = v.pecas.humor;
|
|
96
|
+
const sorteio = gerativo.escolherVisual(humor ? { ...roteiro, pedido: `${roteiro.pedido || ''} ${gerativo.HUMORES[humor].palavras}` } : roteiro, { aleatorio });
|
|
97
|
+
const escolhas = { ...sorteio };
|
|
98
|
+
for (const campo of ['humor', 'paleta', 'display', 'texto', 'mono', 'genero', 'ambiente', 'acento']) if (v.pecas[campo]) escolhas[campo] = v.pecas[campo];
|
|
99
|
+
if (v.pecas.material) escolhas.icones = { ...(sorteio.icones || {}), material: v.pecas.material };
|
|
100
|
+
return { escolhas, escolhidas: Object.keys(v.pecas), avisos };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lerTudo() {
|
|
104
|
+
try { const j = JSON.parse(fs.readFileSync(arquivo(), 'utf8')); return j && typeof j === 'object' ? j : {}; } catch { return {}; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ler(nome) {
|
|
108
|
+
const k = chave(nome);
|
|
109
|
+
return k ? lerTudo()[k] || null : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function listar() {
|
|
113
|
+
return Object.entries(lerTudo()).map(([k, e]) => ({ chave: k, nome: e.nome, descricao: e.descricao || '', pecas: e.pecas }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Guarda o estilo. `autorizado` é a resposta da pessoa: sem ela, nada é escrito. */
|
|
117
|
+
function salvar({ nome, pecas: pedido, descricao, autorizado } = {}) {
|
|
118
|
+
if (autorizado !== true) {
|
|
119
|
+
return { ok: false, error: 'falta o sim da pessoa.', comoResolver: 'pergunte "posso salvar este estilo pra usar nos próximos vídeos?" e só chame de novo com autorizado:true se ela disser que sim.' };
|
|
120
|
+
}
|
|
121
|
+
const k = chave(nome);
|
|
122
|
+
if (!k) return { ok: false, error: 'dê um nome ao estilo (ex: "padaria de manhã").' };
|
|
123
|
+
const v = validar(pedido);
|
|
124
|
+
if (!Object.keys(v.pecas).length) return { ok: false, error: 'nenhuma peça válida para guardar.', avisos: v.avisos };
|
|
125
|
+
const tudo = lerTudo();
|
|
126
|
+
const novo = { nome: String(nome).slice(0, 60), pecas: v.pecas, salvo: new Date().toISOString() };
|
|
127
|
+
if (typeof descricao === 'string' && descricao.trim()) novo.descricao = descricao.slice(0, 200);
|
|
128
|
+
tudo[k] = novo;
|
|
129
|
+
fs.mkdirSync(path.dirname(arquivo()), { recursive: true });
|
|
130
|
+
fs.writeFileSync(arquivo(), JSON.stringify(tudo, null, 2));
|
|
131
|
+
return { ok: true, chave: k, estilo: novo, avisos: v.avisos, comoUsar: `no próximo vídeo, roteiro.estiloVisual:"${k}" traz este estilo (ou {base:"${k}", ...} para misturar).` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** O cardápio que a IA lê antes de montar o estilo. */
|
|
135
|
+
function cardapio() {
|
|
136
|
+
return {
|
|
137
|
+
ok: true,
|
|
138
|
+
total: total(),
|
|
139
|
+
pecas: { ...pecas(), paleta: paletas() },
|
|
140
|
+
salvos: listar(),
|
|
141
|
+
comoUsar: 'monte o estilo pelo ASSUNTO e passe roteiro.estiloVisual {humor, paleta, display, texto, mono, genero, ambiente, acento, material} no estilo gerativo. '
|
|
142
|
+
+ 'Paleta: um nome da lista ou 5 cores suas [escura, assinatura, papel, ousada, ousada]. Misture: {base:"<salvo>", display:"..."} troca só uma peça. '
|
|
143
|
+
+ 'Peça vazia, o PrimoCode completa sem repetir os últimos vídeos. Ficou bom? Pergunte se pode salvar e use acao:"salvar_estilo".',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { pecas, paletas, total, validar, montar, salvar, ler, listar, cardapio, arquivo };
|
package/lib/estudio/gerativo.js
CHANGED
|
@@ -192,6 +192,15 @@ const HUMORES = {
|
|
|
192
192
|
paletas: [['#2A0E12', '#F0E6D6', '#C9A24A'], ['#F1EBE1', '#1B1A17', '#8C1C13']],
|
|
193
193
|
generos: ['jazz', 'soul-rnb', 'classical'], som: 'papel',
|
|
194
194
|
},
|
|
195
|
+
/* (14/10) A padaria caía no editorial — música clássica e mono de jornal —
|
|
196
|
+
porque nenhum humor era de comida. */
|
|
197
|
+
comida: {
|
|
198
|
+
palavras: 'pao paes padaria padeiro cafe cafeteria bolo bolos doce doces confeitaria forno fornada fermentacao massa farinha receita cozinha comida restaurante pizza lanche almoco jantar sabor chef croissant',
|
|
199
|
+
displays: ['Fraunces', 'Playfair Display', 'Bricolage Grotesque'], textos: ['Hanken Grotesk', 'Familjen Grotesk'], monos: ['Space Mono', 'Martian Mono'],
|
|
200
|
+
serifas: ['Instrument Serif', 'Fraunces'], cineticas: ['Anybody', 'Archivo'],
|
|
201
|
+
paletas: [['#F3E6CF', '#3B2416', '#B5651D'], ['#EFE3D0', '#2A1A12', '#C8553D']],
|
|
202
|
+
generos: ['acoustic', 'jazz', 'soul-rnb'], som: 'organico',
|
|
203
|
+
},
|
|
195
204
|
};
|
|
196
205
|
/* A cor que o próprio assunto diz ("por que o céu é AZUL") vira o acento. */
|
|
197
206
|
const CORES_DITAS = { azul: '#2546D9', vermelho: '#C8291E', verde: '#2F7D4F', laranja: '#FF5F1F', amarelo: '#E8B92F', dourado: '#C9A24A', rosa: '#E86A8A', coral: '#FF6F59' };
|
|
@@ -208,11 +217,15 @@ const ICONE_DE = {
|
|
|
208
217
|
notebook: 'celular', pronto: 'check', feito: 'check', certo: 'check', proximo: 'seta', passo: 'seta', adiante: 'seta', ricocheteia: 'seta',
|
|
209
218
|
};
|
|
210
219
|
|
|
220
|
+
/* O humor novo (comida, 14/10) só entra quando o roteiro diz as palavras dele:
|
|
221
|
+
não gasta a semente (a mesma semente segue dando o mesmo vídeo nos projetos
|
|
222
|
+
que já existem) nem tira a vez dos outros num roteiro que não é de comida. */
|
|
223
|
+
const SO_COM_PALAVRA = new Set(['comida']);
|
|
211
224
|
/** O humor do roteiro: as palavras dele contra as de cada humor (empate: a semente). */
|
|
212
225
|
function humorDe(roteiro, s) {
|
|
213
226
|
const n = acertosDeHumor(roteiro);
|
|
214
227
|
const nota = {};
|
|
215
|
-
for (const h of Object.keys(HUMORES)) nota[h] = n[h] + s.prox() * 0.5;
|
|
228
|
+
for (const h of Object.keys(HUMORES)) nota[h] = SO_COM_PALAVRA.has(h) ? (n[h] ? n[h] + 0.25 : -1) : n[h] + s.prox() * 0.5;
|
|
216
229
|
return Object.entries(nota).sort((a, b) => b[1] - a[1])[0][0];
|
|
217
230
|
}
|
|
218
231
|
/** Quantas palavras de cada humor o roteiro diz. */
|
|
@@ -241,7 +254,7 @@ function escolherVisual(roteiro = {}, { aleatorio = Math.random } = {}) {
|
|
|
241
254
|
const marca = require('./marca.js').marcaEmObjeto(roteiro.marca);
|
|
242
255
|
const n = acertosDeHumor(roteiro);
|
|
243
256
|
const max = Math.max(...Object.values(n));
|
|
244
|
-
const humor = variedade.sortear(Object.keys(HUMORES), 'humor-gerativo', { aleatorio, adequacao: (h) => (n[h] + 1) / (max + 1) });
|
|
257
|
+
const humor = variedade.sortear(Object.keys(HUMORES).filter((h) => !SO_COM_PALAVRA.has(h) || n[h]), 'humor-gerativo', { aleatorio, adequacao: (h) => (n[h] + 1) / (max + 1) });
|
|
245
258
|
const de = (campo, outros) => { const m = {}; for (const [h, d] of Object.entries(HUMORES)) for (const x of d[campo]) if (!BANIDAS.includes(x) && (m[x] == null || h === humor)) m[x] = h === humor ? 1 : outros; return m; };
|
|
246
259
|
const escolher = (m, tipo, prefixo) => variedade.sortear(Object.keys(m), tipo, { aleatorio, prefixo, adequacao: m });
|
|
247
260
|
const out = { humor };
|
|
@@ -524,7 +537,8 @@ function pesosDe(c, { curto, palavras, primeira, ultima, tem3d, pergunta, exclam
|
|
|
524
537
|
case 'contraste': return { divisao: 5, duelo: 2.6 };
|
|
525
538
|
case 'citacao': return { citacao: 5, pilha: 1 };
|
|
526
539
|
case 'marca': return { marca: primeira || ultima ? 4.5 : 3, gigante: 2.4, parede: curto ? 2 : 0 };
|
|
527
|
-
|
|
540
|
+
// A marca é UMA linha: "Todo dia às 7h – Padaria Pão Quente" saía em 40 px (14/10). Chamada longa empilha.
|
|
541
|
+
case 'cta': return { pilha: 3, gigante: curto ? 2.6 : 0.6, parede: c.titulo.length <= 16 ? 1.6 : 0, marca: c.titulo.length <= 18 ? 1 : 0, produto: tem3d ? 3.5 : 0 };
|
|
528
542
|
case 'produto': return tem3d ? { produto: 9, pilha: 0.5 } : { pilha: 3, gigante: curto ? 2.5 : 0.5, parede: c.titulo.length <= 12 ? 1.2 : 0 };
|
|
529
543
|
case 'falado': return { palavras: 4, gigante: palavras <= 3 ? 2.6 : 0, pilha: palavras <= 9 ? 1.2 : 0 };
|
|
530
544
|
default: return {
|
|
@@ -628,6 +642,7 @@ const CAMPOS = {
|
|
|
628
642
|
energia: [['#0B0B0B', '#FF4F00', '#F2EEE6', '#1D3BD1', '#C9F23A'], ['#101010', '#E8C547', '#F4EBDD', '#E5322D', '#2A57FF']],
|
|
629
643
|
natureza: [['#0F2A2A', '#F2A541', '#F2EDE2', '#2546D9', '#E4573D'], ['#13233F', '#2546D9', '#F2EDE2', '#F2A541', '#C8553D']],
|
|
630
644
|
luxo: [['#120D0B', '#C9A24A', '#F0E6D6', '#5B1A1F', '#2F4F4F']],
|
|
645
|
+
comida: [['#2A1A12', '#C47A2C', '#F3E6CF', '#7A2E1C', '#E8C27A'], ['#1F1611', '#B5651D', '#F5ECDD', '#5E7D3A', '#E9B44C']],
|
|
631
646
|
};
|
|
632
647
|
function camposDe(marca, humor, s, dita = null, fixa = null) {
|
|
633
648
|
const c = marca.cores || {};
|
|
@@ -2024,4 +2039,5 @@ function contarAcentos(b) {
|
|
|
2024
2039
|
module.exports = { tituloDaFala,
|
|
2025
2040
|
montarGerativo, partituraDe, sonsDoVideo, musicaDe, desenharSom, humorDe, escolherVisual, MOLDURAS, AMBIENTES, ACENTOS_JEITO, ARRANJOS_ICONE, RITMOS_ICONE, MATERIAIS_ICONE, corGenerica, BANIDAS, HUMORES, REGRAS, VARIAVEIS, COMPOSICOES, COMPORTAMENTOS, SAIDAS, ACENTOS, FPS,
|
|
2026
2041
|
especEixos, sorteador, hash, larguraEm, conteudoDe, casar, HEROIS_OBJETO, HEROIS_CAMPO, HEROIS_3D, FAMILIA_HEROI,
|
|
2042
|
+
CAMPOS, escolhasValidas,
|
|
2027
2043
|
};
|
package/lib/estudio/index.js
CHANGED
|
@@ -70,6 +70,8 @@ const glifos = require('./glifos.js');
|
|
|
70
70
|
const personagem = require('./personagem.js');
|
|
71
71
|
const mundo = require('./mundo.js');
|
|
72
72
|
const cenarios = require('./cenarios.js');
|
|
73
|
+
const estilos = require('./estilos.js');
|
|
74
|
+
const { conferirCena } = require('./cena.js');
|
|
73
75
|
const movimentos = require('./movimentos.js');
|
|
74
76
|
const colecao = require('./colecao.js');
|
|
75
77
|
const { escolherEstilo, LEVES } = require('./estilo.js');
|
|
@@ -428,9 +430,13 @@ async function ferramentaPlano(args) {
|
|
|
428
430
|
const usada = diretor.registrarEstrutura(original || args.roteiro);
|
|
429
431
|
projeto.salvar(args.id, { estrutura: usada.estrutura || '-' });
|
|
430
432
|
}
|
|
433
|
+
const quebradas = args.__cenasQuebradas || [];
|
|
431
434
|
return {
|
|
432
435
|
ok: true, duracao: conferida.duracao, ...r.resumo, resumo: plano.resumo(r.plano),
|
|
433
|
-
|
|
436
|
+
...(quebradas.length ? { cenasQuebradas: quebradas } : {}),
|
|
437
|
+
proximo: quebradas.length
|
|
438
|
+
? `${quebradas.length} cena(s) sua(s) não compilaram e ficaram no gerativo: conserte o arquivo (o erro está em cenasQuebradas) e mande o roteiro de novo antes do render.`
|
|
439
|
+
: 'estudio_render com este id. Para ajustar, mande o roteiro de novo (é rápido) — não edite as cenas à mão.',
|
|
434
440
|
};
|
|
435
441
|
}
|
|
436
442
|
|
|
@@ -575,6 +581,10 @@ async function ferramentaMidia(args) {
|
|
|
575
581
|
if (acao === 'salvar_cenario' || acao === 'salvar_cenário') {
|
|
576
582
|
return cenarios.salvar({ nome: args.nome, cenario: args.cenario, autorizado: args.autorizado, bases: Object.keys(mundo.LUGARES) });
|
|
577
583
|
}
|
|
584
|
+
/* OS ESTILOS (estilos.js, 14/10): as peças do gerativo como cardápio — a IA
|
|
585
|
+
monta, mistura e cria; guardar só com o sim da pessoa. */
|
|
586
|
+
if (acao === 'estilos' || acao === 'estilo') return estilos.cardapio();
|
|
587
|
+
if (acao === 'salvar_estilo') return estilos.salvar({ nome: args.nome, pecas: args.pecas || args.estilo, descricao: args.descricao, autorizado: args.autorizado });
|
|
578
588
|
if (acao === 'biblioteca' || acao === 'cardapio' || acao === 'cardápio') return colecao.cardapio();
|
|
579
589
|
if (acao === 'icones3d' || acao === 'icone3d' || acao === 'animacoes' || acao === 'animacao' || acao === 'animação' || acao === 'animações') {
|
|
580
590
|
const ehAnim = /^anima/.test(acao);
|
|
@@ -1263,6 +1273,15 @@ async function sonsReais(p, video) {
|
|
|
1263
1273
|
}
|
|
1264
1274
|
|
|
1265
1275
|
const PROPORCOES = { '16:9': [1920, 1080], '9:16': [1080, 1920], '1:1': [1080, 1080], '4:5': [1080, 1350] };
|
|
1276
|
+
/* A proporção que o PEDIDO disse (14/10: "vertical 9:16" saiu deitado, porque
|
|
1277
|
+
só valia a proporção que o modelo passasse). A do modelo ainda manda. */
|
|
1278
|
+
function proporcaoDoPedido(pedido) {
|
|
1279
|
+
const t = String(pedido || '');
|
|
1280
|
+
if (/\b9\s*[:x×]\s*16\b|\bvertical\b|\breels?\b|\bstor(y|ies)\b|\btik\s?tok\b|\bshorts\b/i.test(t)) return '9:16';
|
|
1281
|
+
if (/\b4\s*[:x×]\s*5\b/.test(t)) return '4:5';
|
|
1282
|
+
if (/\b1\s*[:x×]\s*1\b|\bquadrad[oa]\b/i.test(t)) return '1:1';
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1266
1285
|
|
|
1267
1286
|
/** O roteiro vira plano: baixa o que falta, narra, e chama o compositor. */
|
|
1268
1287
|
/* A ASSINATURA (assinatura.js): o vídeo é medido e conferido contra as
|
|
@@ -1326,6 +1345,13 @@ async function prepararRoteiro(p, args) {
|
|
|
1326
1345
|
fs.copyFileSync(achado, destino);
|
|
1327
1346
|
pt.componente = path.relative(p.pasta, destino).replace(/\\/g, '/');
|
|
1328
1347
|
} else pt.componente = path.relative(p.pasta, achado).replace(/\\/g, '/');
|
|
1348
|
+
// A cena quebrada não derruba o vídeo: sai, a parte fica no gerativo, e o erro volta para a IA (cena.js).
|
|
1349
|
+
const conf = conferirCena(path.join(p.pasta, pt.componente));
|
|
1350
|
+
if (!conf.ok) {
|
|
1351
|
+
(args.__cenasQuebradas = args.__cenasQuebradas || []).push({ componente: pt.componente, erro: conf.erro });
|
|
1352
|
+
projeto.anotar(p.id, 'plano', `componente ${pt.componente} quebrado (${conf.erro}): a parte ficou no gerativo`);
|
|
1353
|
+
delete pt.componente;
|
|
1354
|
+
}
|
|
1329
1355
|
}
|
|
1330
1356
|
// As imagens do pedido (o terminal guardou em referencias.imagens) entram sozinhas, uma vez por projeto.
|
|
1331
1357
|
const jaTem = new Set((p.referencias || []).map((x) => x.de));
|
|
@@ -1354,14 +1380,6 @@ async function prepararRoteiro(p, args) {
|
|
|
1354
1380
|
const semImagem = referencias.semImagem(ref);
|
|
1355
1381
|
let estilo = escolherEstilo({ pedido: pedidoTxt, agente: args.estilo, roteiro: roteiro.estilo, girar: true, semImagem });
|
|
1356
1382
|
if (semImagem && (estilo === 'hibrido' || estilo === 'cinema')) estilo = 'gerativo';
|
|
1357
|
-
/* AS FONTES DA CASA (13/10, o primocode-ele-constroi.mp4 que o Joel aprovou):
|
|
1358
|
-
no gerativo, título em Inter e texto em JetBrains Mono, com as CORES da
|
|
1359
|
-
marca. Só muda se o pedido falar de fonte. */
|
|
1360
|
-
if (estilo === 'gerativo' && !/\bfontes?\b|tipografia|\bserifa/i.test(pedidoTxt)) {
|
|
1361
|
-
roteiro.marca = { ...(roteiro.marca || {}), fontes: { titulo: 'Inter', texto: 'JetBrains Mono' } };
|
|
1362
|
-
}
|
|
1363
|
-
// O estilo da casa no gerativo: chão escuro com brilho, frase média, palavra que entra desfocando (gerativo.js).
|
|
1364
|
-
if (estilo === 'gerativo') roteiro.casa = true;
|
|
1365
1383
|
/* "O vídeo deve ter um 3D do fone" (30/09): pedido de 3D vira o gerativo
|
|
1366
1384
|
com o produto modelado girando numa batida (kit/tres.jsx) — a menos que
|
|
1367
1385
|
o agente tenha escolhido outro estilo pelo nome. */
|
|
@@ -1393,10 +1411,27 @@ async function prepararRoteiro(p, args) {
|
|
|
1393
1411
|
vídeos (09/10, "vício em cores, músicas, fontes"): por PROJETO, como a semente. */
|
|
1394
1412
|
// O assunto do vídeo é o rótulo da moldura (quando ela sai): o título do projeto.
|
|
1395
1413
|
if (estilo === 'gerativo' && !roteiro.titulo && p.titulo) roteiro.titulo = p.titulo;
|
|
1414
|
+
// O estilo que a IA montou (estilos.js) vence o sorteio e o que o projeto tinha guardado.
|
|
1415
|
+
if (estilo === 'gerativo' && roteiro.estiloVisual && !roteiro.escolhasGerativo) {
|
|
1416
|
+
const r = estilos.montar(roteiro.estiloVisual, roteiro);
|
|
1417
|
+
roteiro.escolhasGerativo = r.escolhas;
|
|
1418
|
+
projeto.salvar(p.id, { escolhasGerativo: r.escolhas });
|
|
1419
|
+
if (r.avisos.length) projeto.anotar(p.id, 'estilo', r.avisos.join(' · '));
|
|
1420
|
+
}
|
|
1396
1421
|
if (estilo === 'gerativo' && !roteiro.escolhasGerativo) {
|
|
1397
1422
|
roteiro.escolhasGerativo = p.escolhasGerativo || gerativo.escolherVisual(roteiro);
|
|
1398
1423
|
if (!p.escolhasGerativo) projeto.salvar(p.id, { escolhasGerativo: roteiro.escolhasGerativo });
|
|
1399
1424
|
}
|
|
1425
|
+
/* O ESTILO DA CASA (13/10, o primocode-ele-constroi.mp4 que o Joel aprovou):
|
|
1426
|
+
chão escuro com brilho, frase média, palavra que entra desfocando, Inter
|
|
1427
|
+
e JetBrains Mono. Vale para o vídeo de TECNOLOGIA (o do PrimoCode) quando
|
|
1428
|
+
a IA não montou estilo: em todo vídeo, a padaria saía igual — "animações
|
|
1429
|
+
tudo igual, cores sempre as mesmas, sem ícones" (14/10). */
|
|
1430
|
+
const casa = estilo === 'gerativo' && !roteiro.estiloVisual && (roteiro.escolhasGerativo || {}).humor === 'tecnologia';
|
|
1431
|
+
if (casa && !/\bfontes?\b|tipografia|\bserifa/i.test(pedidoTxt)) {
|
|
1432
|
+
roteiro.marca = { ...(roteiro.marca || {}), fontes: { titulo: 'Inter', texto: 'JetBrains Mono' } };
|
|
1433
|
+
}
|
|
1434
|
+
if (casa) roteiro.casa = true;
|
|
1400
1435
|
if (ref.modo === 'fechado' && !roteiro.busca && referencias.termos().length) roteiro.busca = referencias.termos();
|
|
1401
1436
|
/* A BIBLIOTECA (biblioteca.js): sem fonte e cor da marca, o híbrido, o
|
|
1402
1437
|
cinema e o impacto caíam sempre no Inter e no azul e roxo — "os vídeos
|
|
@@ -1409,7 +1444,7 @@ async function prepararRoteiro(p, args) {
|
|
|
1409
1444
|
// Os estilos de motion usam os efeitos leves e a trilha do ende.app; os de corte, os cheios.
|
|
1410
1445
|
const ehMotion = LEVES.has(estilo);
|
|
1411
1446
|
const modo = (args.modo || roteiro.modo || (p.formato === 'slides' || p.formato === 'arte' ? 'slides' : 'video'));
|
|
1412
|
-
const [largura, altura] = PROPORCOES[roteiro.proporcao || args.proporcao] || (modo === '
|
|
1447
|
+
const [largura, altura] = PROPORCOES[roteiro.proporcao || args.proporcao] || (modo === 'video' && PROPORCOES[proporcaoDoPedido(p.pedido)]) || PROPORCOES['16:9'];
|
|
1413
1448
|
|
|
1414
1449
|
const iconesOk = new Set();
|
|
1415
1450
|
for (const nome of composicao.iconesDoRoteiro(roteiro)) {
|
|
@@ -1919,6 +1954,17 @@ async function prepararRoteiro(p, args) {
|
|
|
1919
1954
|
'-af', 'afade=t=in:st=0:d=0.4,loudnorm=I=-18:TP=-2:LRA=11', '-ar', '44100', norm], { timeout: 5 * 60000 });
|
|
1920
1955
|
if (c.ok && fs.existsSync(norm)) trilha = path.relative(p.pasta, norm).replace(/\\/g, '/');
|
|
1921
1956
|
}
|
|
1957
|
+
/* SEM ÍCONE NENHUM (14/10, a padaria: "sem ícones"): o modelo grátis não
|
|
1958
|
+
dá herói, e o gerativo de comida não tem forma abstrata — o vídeo
|
|
1959
|
+
saía só com texto. Na parte em que o modelo não disse nada (nem
|
|
1960
|
+
"nenhum"), o ícone 3D que É a coisa que a fala nomeia ("pão" → bread). */
|
|
1961
|
+
const autoHerois = [];
|
|
1962
|
+
for (const pt of partes) {
|
|
1963
|
+
if (!pt || pt.heroi != null || pt.icone != null) continue;
|
|
1964
|
+
const id = colecao.melhorIcone3d([pt.titulo, pt.destaque, pt.fala, pt.texto].filter(Boolean).join(' '), 80);
|
|
1965
|
+
if (id && !autoHerois.includes(id)) { pt.heroi = id; autoHerois.push(id); }
|
|
1966
|
+
}
|
|
1967
|
+
if (autoHerois.length) projeto.anotar(p.id, 'icones', `sem herói do modelo, a biblioteca achou: ${autoHerois.join(', ')}`);
|
|
1922
1968
|
/* O herói que o roteiro pediu do Iconify ("mdi:bread-slice"): o desenho é
|
|
1923
1969
|
baixado aqui (fica em midia/icones); o que não baixar, a batida fica sem. */
|
|
1924
1970
|
const icones = {};
|
|
@@ -2402,7 +2448,7 @@ async function ferramentaSite(args) {
|
|
|
2402
2448
|
return r;
|
|
2403
2449
|
}
|
|
2404
2450
|
|
|
2405
|
-
module.exports = {
|
|
2451
|
+
module.exports = { proporcaoDoPedido,
|
|
2406
2452
|
vazamentoNoComeco, variarIcones, objetosDoRoteiro,
|
|
2407
2453
|
ferramentaProjeto, ferramentaPlano, ferramentaMidia, ferramentaNarrar,
|
|
2408
2454
|
ferramentaVideo, ferramentaRender, ferramentaSite,
|
package/lib/tools.js
CHANGED
|
@@ -594,6 +594,12 @@ async function despachar(name, args, ctx) {
|
|
|
594
594
|
{
|
|
595
595
|
switch (name) {
|
|
596
596
|
case 'write_file': {
|
|
597
|
+
// O modelo grátis manda o caminho com outro nome (file_path, caminho) ou nenhum,
|
|
598
|
+
// e o write caía na própria pasta: EISDIR, e ele tentava de novo igual (14/10).
|
|
599
|
+
if (!args.path) args.path = args.file_path || args.filePath || args.caminho || args.arquivo || args.file || '';
|
|
600
|
+
if (!String(args.path).trim()) {
|
|
601
|
+
return { ok: false, error: 'faltou o caminho do arquivo em "path".', comoResolver: 'chame write_file de novo com path:"pasta/nome.ext" e content.' };
|
|
602
|
+
}
|
|
597
603
|
// Vídeo e áudio NÃO se escrevem com write_file. Num pedido
|
|
598
604
|
// real o agente criou um "video.mp4" de 43 bytes de texto e
|
|
599
605
|
// disse "vídeo criado com sucesso" — um arquivo falso, morto,
|
|
@@ -610,6 +616,11 @@ async function despachar(name, args, ctx) {
|
|
|
610
616
|
}
|
|
611
617
|
const filePath = resolveInProject(args.path, projectDir);
|
|
612
618
|
const content = args.content || '';
|
|
619
|
+
try {
|
|
620
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
621
|
+
return { ok: false, error: `"${args.path}" é uma PASTA, não um arquivo.`, comoResolver: `ponha o nome do arquivo no fim: path:"${String(args.path).replace(/\/+$/, '')}/index.html" (ou o nome que for).` };
|
|
622
|
+
}
|
|
623
|
+
} catch { /* não existe: vai ser criado */ }
|
|
613
624
|
const created = !fs.existsSync(filePath);
|
|
614
625
|
// Reescrever um arquivo que já existe é a operação mais
|
|
615
626
|
// perigosa do agente: o conteúdo antigo some inteiro. Ler o
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.10.
|
|
3
|
+
"version": "10.10.1",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/rolar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js && node test/estilos.test.js && node test/proporcao.test.js && node test/cena.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -22,59 +22,58 @@ Não faça nada disto, em nenhum vídeo:
|
|
|
22
22
|
|
|
23
23
|
## O que separa motion bom de slide animado
|
|
24
24
|
|
|
25
|
-
**A câmera existe.**
|
|
26
|
-
|
|
27
|
-
quase nenhum vídeo automático faz isso.
|
|
25
|
+
**A câmera existe.** Um zoom lento de 1.0 para 1.08 na cena inteira dá vida a
|
|
26
|
+
um quadro parado.
|
|
28
27
|
|
|
29
28
|
**Nada entra do nada.** Um elemento entra vindo de algum lugar (de baixo, de
|
|
30
|
-
trás, de fora do quadro), com `
|
|
29
|
+
trás, de fora do quadro), com `spring` do Remotion ou easing de saída. Movimento
|
|
31
30
|
linear é a assinatura da máquina: no mundo físico nada começa e para na mesma
|
|
32
31
|
velocidade.
|
|
33
32
|
|
|
34
|
-
**Profundidade.**
|
|
35
|
-
|
|
36
|
-
rápido. Com `perspectiva` na cena, `rotacaoX`/`rotacaoY`/`z` viram 3D de
|
|
37
|
-
verdade.
|
|
33
|
+
**Profundidade.** Parallax (fundo devagar, frente rápido); `perspective` com
|
|
34
|
+
`rotateX`/`translateZ`, ou three, para 3D de verdade.
|
|
38
35
|
|
|
39
|
-
**Ritmo.**
|
|
40
|
-
a 4s. Fecho: 2s. Variar a duração é o que cria ritmo; repetir cria sono.
|
|
36
|
+
**Ritmo.** Informação simples 1,5–2,5 s; frase para ler 3–4 s; fecho 2 s.
|
|
41
37
|
|
|
42
38
|
**Silêncio visual.** Um quadro com uma frase e mais nada vale mais que um com
|
|
43
39
|
seis elementos. Espaço vazio é composição, não desperdício.
|
|
44
40
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
41
|
+
## O caminho: você escreve cada cena
|
|
42
|
+
|
|
43
|
+
Motion nesse nível não sai de molde: cada cena é UMA ideia desenhada para a
|
|
44
|
+
fala — a interface do produto montando, o número que cresce, o objeto em 3D,
|
|
45
|
+
o gráfico que mostra a ideia, a palavra gigante que se transforma. Quem
|
|
46
|
+
desenha é você, em React.
|
|
47
|
+
|
|
48
|
+
1. `estudio_projeto` (criar). A marca tem site? `estudio_site` mede cores e fontes.
|
|
49
|
+
2. `estudio_midia acao:"estilos"`: monte a identidade pelo assunto em
|
|
50
|
+
`roteiro.estiloVisual` (humor, paleta, display, texto…). Um vídeo, uma
|
|
51
|
+
identidade: 2 chãos, 1 acento, 2 fontes.
|
|
52
|
+
3. `estudio_narrar` com o roteiro: os tempos MEDIDOS de cada fala.
|
|
53
|
+
4. Para cada parte, `write_file` com `path:"componentes/<nome>.jsx"`:
|
|
54
|
+
`export default function Cena({ el, t, fps })`, `t` em segundos desde o
|
|
55
|
+
começo da parte. Em `el.dados`: `titulo`, `fala`, `palavras` (cada uma com
|
|
56
|
+
o seu `t`, para entrar junto com a voz), `duracao`, `cores` {fundo, texto,
|
|
57
|
+
acento}, `fontes`, `largura`, `altura`. Imports só de react, remotion,
|
|
58
|
+
@remotion/*, three. Cor e fonte vêm de `el.dados`, nunca inventadas na cena.
|
|
59
|
+
5. `estudio_plano` com o roteiro: `estiloVisual` e, em cada parte,
|
|
60
|
+
`componente:"componentes/<nome>.jsx"`. Voltou `cenasQuebradas`? Conserte o
|
|
61
|
+
arquivo e mande o roteiro de novo.
|
|
62
|
+
6. `estudio_render`. A voz, a trilha, os sons e o fundo são do Estúdio.
|
|
63
|
+
|
|
64
|
+
Parte sem `componente` cai no gerativo (a tipografia anima sozinha): é a
|
|
65
|
+
reserva, não o objetivo. Sem imagem é o padrão (foto só se pedirem).
|
|
63
66
|
|
|
64
67
|
## Cor e tipo
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
não as que você imagina que ela usa.
|
|
69
|
+
A paleta sai do ASSUNTO, no `estiloVisual` (um nome do cardápio ou 5 cores
|
|
70
|
+
suas). Se a marca existe, as cores medidas pelo `estudio_site` mandam — não
|
|
71
|
+
as que você imagina que ela usa.
|
|
70
72
|
|
|
71
|
-
Duas fontes, no máximo:
|
|
72
|
-
que se lê. `estudio_midia acao:"fontes"` mostra um leque grande — sair do
|
|
73
|
-
Inter/Montserrat é metade do trabalho de não parecer genérico.
|
|
73
|
+
Duas fontes, no máximo: display para o que grita, neutra para o que se lê.
|
|
74
74
|
|
|
75
75
|
## Antes de renderizar
|
|
76
76
|
|
|
77
|
-
- Todo `src` aponta para arquivo que existe na pasta (baixe antes).
|
|
78
77
|
- Os tempos das cenas batem com os tempos MEDIDOS da narração.
|
|
79
78
|
- Nenhuma cena tem a mesma composição da anterior.
|
|
80
79
|
- Leia o seu próprio plano e pergunte: em que este vídeo é diferente do
|