primocode 9.10.0 → 10.0.0
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 +30 -6
- package/bin/primocode.js +100 -341
- package/lib/act.js +245 -23
- package/lib/browser.js +17 -0
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +4 -2
- package/lib/estudio/diretor.js +9 -1
- package/lib/estudio/estilo.js +51 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/index.js +41 -18
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/index.jsx +11 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +72 -0
- package/lib/estudio/lancamento.js +413 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/remotion.js +87 -43
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/vox.js +385 -0
- package/lib/fala.js +1 -94
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +8 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +79 -20
- package/lib/ui.js +13 -6
- package/lib/video/analise.js +89 -0
- package/lib/video/cli.js +74 -0
- package/lib/video/delta.js +90 -0
- package/lib/video/edits.js +379 -0
- package/lib/video/filters.js +247 -0
- package/lib/video/ops.js +322 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/render.js +221 -0
- package/lib/video/texto.js +142 -0
- package/lib/video/timeline.js +310 -0
- package/lib/video/transcript.js +124 -0
- package/lib/voz.js +75 -223
- package/package.json +2 -3
- package/skills/motion-de-primeira/SKILL.md +3 -4
- package/studio/texto.py +208 -0
- package/voz/primo_voz/__main__.py +96 -236
- package/voz/primo_voz/transcrever.py +31 -1
- package/app/ansi.js +0 -55
- package/app/app.css +0 -500
- package/app/app.js +0 -1017
- package/app/icone.svg +0 -6
- package/app/index.html +0 -220
- package/app/primo-marca.js +0 -192
- package/lib/app.js +0 -615
- package/lib/icone.js +0 -263
- package/lib/instalar-app.js +0 -231
- package/lib/painel.js +0 -291
- package/voz/primo_voz/desperta.py +0 -195
- package/voz/primo_voz/orbe.py +0 -406
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vox.js — o estilo VOX: explainer editorial, narração ilustrada.
|
|
3
|
+
*
|
|
4
|
+
* "O vídeo é uma narração ilustrada. Cada elemento existe porque uma
|
|
5
|
+
* palavra pediu — e entra no instante exato em que ela é dita."
|
|
6
|
+
* — guia MOTION VOX STYLE com Remotion
|
|
7
|
+
*
|
|
8
|
+
* Uma cena por parte, com CORTE SECO no beat. Cada cena é composta como
|
|
9
|
+
* revista, não como slide:
|
|
10
|
+
*
|
|
11
|
+
* · 30 a 40% do quadro é fundo puro, e a banda de cima nunca é invadida;
|
|
12
|
+
* · uma âncora grande (30 a 40% da largura), o resto três vezes menor;
|
|
13
|
+
* · UM acento, em no máximo três pontos; o resto é tinta sobre papel;
|
|
14
|
+
* · tudo imprime no papel: sombra de contato, tinta, grão e vinheta;
|
|
15
|
+
* · a câmera conta a frase: dolly do perto para o longe na abertura,
|
|
16
|
+
* empurrão no clímax, e acelera antes do corte;
|
|
17
|
+
* · parallax como consequência da câmera: quatro planos (0,12 · 0,40 ·
|
|
18
|
+
* 0,55 · 1,25), com o desfoque da lente em cada um.
|
|
19
|
+
*
|
|
20
|
+
* Cada tipo de parte vira um dos oito dispositivos tipográficos do kit
|
|
21
|
+
* (kit/vox.jsx):
|
|
22
|
+
*
|
|
23
|
+
* abertura cartão-espécime (termo curto) ou highlight na letra, no escuro
|
|
24
|
+
* titulo highlight na letra + source tag
|
|
25
|
+
* numero palavra ancorada gigante (o número) + o rótulo carimbando
|
|
26
|
+
* lista lista com travessão (o anterior apaga para cinza)
|
|
27
|
+
* passos idem, numerada 01 — 02 — 03
|
|
28
|
+
* contraste o "não" riscado à caneta, o "sim" carimba no acento
|
|
29
|
+
* citacao o documento lido em cascata, marca-texto e círculo
|
|
30
|
+
* imagem a foto na mesa, a pill branca e a linha-guia
|
|
31
|
+
* grafico as barras em tinta, a maior circulada
|
|
32
|
+
* cta texto-textura de fundo, a chamada carimbada e a pill
|
|
33
|
+
* logo a marca e o nome em espécime
|
|
34
|
+
* cores os cartões de cor sobre o papel
|
|
35
|
+
* produto o cartão do produto sobre a mesa
|
|
36
|
+
*
|
|
37
|
+
* Puro: roteiro, tempos da voz e arquivos entram; o plano sai.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
'use strict';
|
|
41
|
+
|
|
42
|
+
const { caber } = require('./composicao.js');
|
|
43
|
+
const { misturarCor, ehCor } = require('./morfar.js');
|
|
44
|
+
const { tempoDasPartes, gatilhos, palavrasNaCena, volumesDaTrilha, r2 } = require('./ritmo.js');
|
|
45
|
+
|
|
46
|
+
const FPS = 30;
|
|
47
|
+
const k = (t, v, e) => (e ? { t: r2(t), v, e } : { t: r2(t), v });
|
|
48
|
+
const luz = (hex) => {
|
|
49
|
+
const h = String(hex || '').replace('#', '');
|
|
50
|
+
const n = h.length === 3 ? h.split('').map((c) => c + c).join('') : h.slice(0, 6);
|
|
51
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) || 0);
|
|
52
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
53
|
+
};
|
|
54
|
+
const itensDe = (p) => (p.itens || []).map((i) => (typeof i === 'string' ? i : i.texto || i.titulo || '')).filter(Boolean);
|
|
55
|
+
const semAcento = (w) => String(w || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
56
|
+
const PEQUENAS = new Set(['a', 'o', 'as', 'os', 'um', 'uma', 'de', 'do', 'da', 'dos', 'das', 'e', 'em', 'no', 'na', 'nos', 'nas', 'que', 'se', 'por', 'pra', 'para', 'com', 'ao', 'the', 'of', 'to', 'and']);
|
|
57
|
+
|
|
58
|
+
/** A palavra-tese de uma frase: a pedida, ou a mais longa das últimas. */
|
|
59
|
+
function tese(frase, pedida) {
|
|
60
|
+
const ws = String(frase || '').split(/\s+/).filter(Boolean);
|
|
61
|
+
if (pedida && ws.some((w) => semAcento(w) === semAcento(pedida))) return pedida;
|
|
62
|
+
let melhor = '';
|
|
63
|
+
ws.forEach((w, i) => { const c = semAcento(w); if (!PEQUENAS.has(c) && c.length + i * 0.6 >= semAcento(melhor).length) melhor = w; });
|
|
64
|
+
return melhor.replace(/[.,;:!?]+$/, '');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Quebra um texto em linhas de até `n` caracteres, palavra por palavra. */
|
|
68
|
+
function quebrar(texto, n) {
|
|
69
|
+
const linhas = [];
|
|
70
|
+
let atual = '';
|
|
71
|
+
for (const w of String(texto || '').split(/\s+/).filter(Boolean)) {
|
|
72
|
+
if (atual && (atual + ' ' + w).length > n) { linhas.push(atual); atual = w; } else atual = atual ? atual + ' ' + w : w;
|
|
73
|
+
}
|
|
74
|
+
if (atual) linhas.push(atual);
|
|
75
|
+
return linhas;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** "Termo — definição" (ou "termo: definição") vira {termo, def}. */
|
|
79
|
+
function termoDef(t) {
|
|
80
|
+
const m = String(t).match(/^(.{1,40}?)\s*(?:—|–|-|:)\s+(.+)$/);
|
|
81
|
+
return m ? { termo: m[1], def: m[2] } : { termo: String(t) };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {object} op
|
|
86
|
+
* @param {object} op.roteiro { marca, partes: [{tipo, titulo, sub, itens, valor, rotulo, sim, nao, autor, destaque, fonte, barras, link, src}] }
|
|
87
|
+
* @param {Array} op.tempos [{de, ate, palavras}] por parte (da narração)
|
|
88
|
+
* @param {object} op.imagens { indiceDaParte: {src, tipo} }
|
|
89
|
+
*/
|
|
90
|
+
function montarVox({ roteiro, largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null, imagens = {} }) {
|
|
91
|
+
const partes = (roteiro && roteiro.partes) || [];
|
|
92
|
+
if (!partes.length) return { ok: false, error: 'o roteiro veio sem partes.' };
|
|
93
|
+
const W = largura, H = altura, deitado = W >= H;
|
|
94
|
+
const b = Math.min(W, H) / 1080;
|
|
95
|
+
const M = Math.round(Math.min(W, H) * 0.075);
|
|
96
|
+
const TOPO = Math.round(H * (deitado ? 0.17 : 0.2)); // a banda intocada
|
|
97
|
+
|
|
98
|
+
// ── a paleta: papel, tinta e UM acento ─────────────────────────────
|
|
99
|
+
const marca = roteiro.marca || {};
|
|
100
|
+
const cores = marca.cores || {};
|
|
101
|
+
const PAPEL = ehCor(cores.fundo) && luz(cores.fundo) > 0.78 ? cores.fundo : '#ece8df';
|
|
102
|
+
const TINTA = ehCor(cores.tinta) && luz(cores.tinta) < 0.3 ? cores.tinta : '#171614';
|
|
103
|
+
let ACENTO = ehCor(cores.destaque) ? cores.destaque : '#d23a2a';
|
|
104
|
+
for (let i = 0; i < 5 && luz(ACENTO) > 0.62; i++) ACENTO = misturarCor(ACENTO, '#000000', 0.2);
|
|
105
|
+
const CINZA = misturarCor(PAPEL, TINTA, 0.3);
|
|
106
|
+
const ESCURO = '#121110';
|
|
107
|
+
const F = {
|
|
108
|
+
titulo: (marca.fontes && marca.fontes.titulo) || 'Inter Tight',
|
|
109
|
+
texto: (marca.fontes && marca.fontes.texto) || 'Inter Tight',
|
|
110
|
+
mono: 'JetBrains Mono', serifa: 'Newsreader',
|
|
111
|
+
};
|
|
112
|
+
const base = { fonte: F.titulo, fonteMono: F.mono, cor: TINTA, corAcento: ACENTO };
|
|
113
|
+
|
|
114
|
+
// ── o tempo ────────────────────────────────────────────────────────
|
|
115
|
+
const leitura = (p) => 1.6 + 0.3 * String([p.titulo, p.sub, p.valor, p.rotulo, p.nao, p.sim, ...itensDe(p)].filter(Boolean).join(' ')).split(/\s+/).length;
|
|
116
|
+
const { ini, dur, total, temVoz } = tempoDasPartes(partes, tempos, { semVoz: leitura });
|
|
117
|
+
const efeitos = [];
|
|
118
|
+
const som = (t, nome, volume) => { if (sons[nome]) efeitos.push({ t: r2(Math.max(0, t)), src: sons[nome], volume }); };
|
|
119
|
+
|
|
120
|
+
// ── as quatro profundidades, e o que mora em cada uma ──────────────
|
|
121
|
+
const CAMADAS = [{ p: 0.12, desfoque: 2.2 }, { p: 0.4, desfoque: 0.9 }, { p: 0.55, desfoque: 0, sujeito: true }, { p: 1.25, desfoque: 3.4 }];
|
|
122
|
+
const papel = (escuro) => (escuro
|
|
123
|
+
? { tipo: 'forma', forma: 'retangulo', x: '-8%', y: '-8%', largura: '116%', altura: '116%', camada: 0,
|
|
124
|
+
fundo: { tipo: 'gradiente', radial: true, centro: '38% 45%', cores: ['#2a2723', ESCURO] } }
|
|
125
|
+
: { tipo: 'kit', peca: 'papel', cor: PAPEL, sobra: 1.16, camada: 0 });
|
|
126
|
+
/* A câmera conta a frase. Dolly do perto para o longe na abertura (3,4 →
|
|
127
|
+
1,0), empurrão no clímax, e nos outros um avanço lento; nos últimos
|
|
128
|
+
quadros ela ACELERA — é o que vende o corte. Bezier longa, nunca mola. */
|
|
129
|
+
const camera = (d, modo, i) => {
|
|
130
|
+
const lado = i % 2 ? 1 : -1;
|
|
131
|
+
const fimLento = Math.max(0.4, d - 0.3);
|
|
132
|
+
if (modo === 'abertura') {
|
|
133
|
+
return { zoom: [k(0, 3.4), k(Math.min(1.8, d * 0.55), 1.0, 'camera'), k(fimLento, 1.03, 'camera'), k(d, 1.06, 'acelera')],
|
|
134
|
+
x: [k(0, 0), k(d, lado * 10 * b, 'camera')] };
|
|
135
|
+
}
|
|
136
|
+
const z1 = modo === 'climax' ? 1.14 : 1.045;
|
|
137
|
+
return { zoom: [k(0, 1.0), k(fimLento, z1, 'camera'), k(d, z1 + 0.035, 'acelera')],
|
|
138
|
+
x: [k(0, -lado * 14 * b), k(d, lado * 14 * b, 'camera')],
|
|
139
|
+
rotacao: [k(0, -lado * 0.3), k(d, lado * 0.3, 'camera')] };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/* O CORTE NO BEAT dentro da parte longa: passou de 5 s, a câmera pula de
|
|
143
|
+
enquadramento numa palavra do meio (dois quadros-chave a 1 quadro). */
|
|
144
|
+
const corteInterno = (cam, d, tempo, inicio) => {
|
|
145
|
+
if (d <= 5.2) return cam;
|
|
146
|
+
const ws = palavrasNaCena(tempo, inicio).filter((w) => w.t > d * 0.4 && w.t < d * 0.7);
|
|
147
|
+
const tc = ws.length ? ws[0].t : r2(d * 0.5);
|
|
148
|
+
const z = (cam.zoom || []).filter((q) => q.t < tc);
|
|
149
|
+
const zAntes = z.length ? z[z.length - 1].v : 1;
|
|
150
|
+
const zFim = cam.zoom[cam.zoom.length - 1].v;
|
|
151
|
+
cam.zoom = [...z, k(tc - 1 / FPS, zAntes), k(tc, zAntes + 0.09), k(d, zFim + 0.09, 'camera')];
|
|
152
|
+
return cam;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// ── as partes ──────────────────────────────────────────────────────
|
|
156
|
+
/* Em pé a âncora ocupa a largura toda, então ela mede um pouco menos: o
|
|
157
|
+
empurrão da câmera (até 8%) não pode levar a última letra para fora. */
|
|
158
|
+
const larguraAncora = deitado ? W * 0.64 : (W - 2 * M) * 0.9;
|
|
159
|
+
const cenas = partes.map((p, i) => {
|
|
160
|
+
const d = dur[i];
|
|
161
|
+
const t0 = ini[i];
|
|
162
|
+
const tp = tempos[i];
|
|
163
|
+
const g = (textos, op) => gatilhos(textos, tp, t0, d, op);
|
|
164
|
+
const esquerda = i % 2 === 0 || !deitado;
|
|
165
|
+
const xA = esquerda ? M : W - M - larguraAncora; // a âncora de um lado, o vazio do outro
|
|
166
|
+
const els = [];
|
|
167
|
+
let modo = 'normal', escuro = false, origem = null;
|
|
168
|
+
const titulo = p.titulo || p.texto || '';
|
|
169
|
+
const semMidia = p.tipo === 'imagem' && !(imagens[i] || p.src);
|
|
170
|
+
const tipo = semMidia ? 'titulo' : p.tipo;
|
|
171
|
+
const fonteTag = (texto, y, t, extra = {}) => els.push({ tipo: 'kit', peca: 'fonte', texto, x: xA, y: r2(y), t, tamanho: r2(24 * b), ...base, cor: misturarCor(TINTA, PAPEL, 0.35), ...extra });
|
|
172
|
+
|
|
173
|
+
switch (tipo) {
|
|
174
|
+
case 'abertura': {
|
|
175
|
+
escuro = true; modo = 'abertura';
|
|
176
|
+
const curto = titulo.replace(/\s+/g, '').length <= 12 && titulo.split(/\s+/).length <= 2;
|
|
177
|
+
if (curto) {
|
|
178
|
+
const n = titulo.length;
|
|
179
|
+
const tam = Math.min(170 * b, (W - 2 * M) / (n * 1.06));
|
|
180
|
+
const [tt] = g([titulo]);
|
|
181
|
+
els.push({ tipo: 'kit', peca: 'especime', termo: titulo, sub: p.sub || '', x: W / 2, y: H / 2, t: Math.min(0.35, tt), tamanho: r2(tam),
|
|
182
|
+
...base, cor: '#f2eee6', corSub: '#9d968a' });
|
|
183
|
+
origem = [W / 2 - (n * tam * 1.02) / 2 + tam * 0.4, H / 2 - (p.sub ? tam * 0.34 : 0)];
|
|
184
|
+
} else {
|
|
185
|
+
const fit = caber(titulo, { largura: W - 2 * M, linhas: 3, max: 150 * b, min: 44 * b, fonte: F.titulo });
|
|
186
|
+
const alt = fit.linhas * fit.tamanho * 1.04;
|
|
187
|
+
els.push({ tipo: 'kit', peca: 'letra', texto: titulo, destaque: tese(titulo, p.destaque), x: M, y: r2(H * 0.55 - alt / 2), largura: W - 2 * M,
|
|
188
|
+
tamanho: fit.tamanho, t: 0.3, tDestaque: g([tese(titulo, p.destaque)])[0], ...base, cor: '#f2eee6' });
|
|
189
|
+
if (p.sub) fonteTag(p.sub, H * 0.55 + alt / 2 + 40 * b, 1.0, { cor: '#9d968a' });
|
|
190
|
+
origem = [M + 40 * b, H * 0.55];
|
|
191
|
+
}
|
|
192
|
+
som(t0 + 0.3, 'toque', 0.3);
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case 'numero': {
|
|
196
|
+
modo = 'climax';
|
|
197
|
+
const v = String(p.valor || titulo);
|
|
198
|
+
const tamN = caber(v, { largura: larguraAncora * 0.9, linhas: 1, max: 400 * b, min: 110 * b, fonte: F.titulo }).tamanho;
|
|
199
|
+
const rot = p.rotulo || (p.valor ? titulo : '');
|
|
200
|
+
const [tv, tr] = g([v, rot || v]);
|
|
201
|
+
const yN = H * 0.62 - tamN;
|
|
202
|
+
els.push({ tipo: 'kit', peca: 'palavra', palavras: [{ texto: v, t: tv, acento: true, peso: 800 }], x: xA, y: r2(yN), tamanho: tamN, ...base, sublinhar: false });
|
|
203
|
+
let yFim = yN + tamN * 1.02;
|
|
204
|
+
if (rot) {
|
|
205
|
+
const fr = caber(rot, { largura: larguraAncora, linhas: 2, max: 76 * b, min: 34 * b, fonte: F.texto });
|
|
206
|
+
const tamR = fr.tamanho;
|
|
207
|
+
yFim += fr.linhas * tamR * 1.08;
|
|
208
|
+
els.push({ tipo: 'kit', peca: 'palavra', x: xA, y: r2(yN + tamN * 1.02), largura: larguraAncora, tamanho: tamR, ...base, sublinhar: false,
|
|
209
|
+
palavras: rot.split(/\s+/).map((w, j) => ({ texto: w, t: r2(tr + j * 4 / FPS), peso: PEQUENAS.has(semAcento(w)) ? 300 : 500 })) });
|
|
210
|
+
}
|
|
211
|
+
// A fonte vem DEPOIS do rótulo, na altura que ele de fato ocupa (em pé ele quebra em duas linhas).
|
|
212
|
+
if (p.sub || p.fonte) fonteTag(p.fonte || p.sub, yFim + 36 * b, Math.min(d - 0.8, tr + 0.6));
|
|
213
|
+
origem = [xA + tamN, yN + tamN / 2];
|
|
214
|
+
som(t0 + tv, 'toque', 0.35);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'lista': case 'passos': case 'contraste': {
|
|
218
|
+
let itens;
|
|
219
|
+
if (tipo === 'contraste') itens = [{ termo: p.nao || '—', riscar: true }, { termo: p.sim || titulo }];
|
|
220
|
+
else itens = itensDe(p).slice(0, 5).map((x, j) => (tipo === 'passos' ? { termo: String(j + 1).padStart(2, '0'), def: x } : termoDef(x)));
|
|
221
|
+
const maior = itens.reduce((a, x) => ((x.termo + (x.def ? ' — ' + x.def : '')).length > a.length ? x.termo + (x.def ? ' — ' + x.def : '') : a), '');
|
|
222
|
+
// Uma linha por item quando cabe grande; senão, duas.
|
|
223
|
+
const umaLinha = caber(maior, { largura: larguraAncora * (deitado ? 1.05 : 1), linhas: 1, max: (tipo === 'contraste' ? 170 : itens.length > 3 ? 80 : 96) * b, min: 30 * b, fonte: F.titulo });
|
|
224
|
+
const tam = umaLinha.tamanho >= 58 * b || tipo === 'contraste' ? umaLinha.tamanho
|
|
225
|
+
: caber(maior, { largura: larguraAncora * (deitado ? 1.05 : 1), linhas: 2, max: 80 * b, min: 30 * b, fonte: F.titulo }).tamanho;
|
|
226
|
+
const ts = g(itens.map((x) => (tipo === 'passos' ? x.def : x.termo)), { t0: titulo && tipo !== 'contraste' ? 0.6 : 0.3 });
|
|
227
|
+
itens.forEach((x, j) => { x.t = ts[j]; });
|
|
228
|
+
const linhasEst = itens.reduce((n, x) => n + Math.ceil(((x.termo + (x.def ? ' — ' + x.def : '')).length * tam * 0.56) / larguraAncora), 0);
|
|
229
|
+
const alt = linhasEst * tam * 1.08 + itens.length * tam * 0.34;
|
|
230
|
+
const y = Math.max(TOPO + 60 * b, H * 0.58 - alt / 2);
|
|
231
|
+
if (titulo && tipo !== 'contraste') fonteTag(titulo, y - 70 * b, 0.15);
|
|
232
|
+
els.push({ tipo: 'kit', peca: 'lista', itens, x: xA, y: r2(y), largura: larguraAncora, tamanho: tam, ...base, corVelha: CINZA });
|
|
233
|
+
origem = [xA + 60 * b, y + alt / 2];
|
|
234
|
+
itens.forEach((x) => som(t0 + x.t, 'toque', 0.22));
|
|
235
|
+
if (tipo === 'contraste') som(t0 + itens[0].t + 0.35, 'sopro', 0.18);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
case 'citacao': {
|
|
239
|
+
const frase = `“${titulo}”`;
|
|
240
|
+
const porLinha = deitado ? 30 : 22;
|
|
241
|
+
const linhas = quebrar(frase, porLinha).slice(0, 6);
|
|
242
|
+
const alvo = semAcento(tese(titulo, p.destaque));
|
|
243
|
+
const marca = Math.max(0, linhas.findIndex((l) => l.split(/\s+/).some((w) => semAcento(w) === alvo)));
|
|
244
|
+
const docW = deitado ? W * 0.56 : W - 2 * M;
|
|
245
|
+
const tam = Math.min(64 * b, docW / (porLinha * 0.5 + 2.4));
|
|
246
|
+
const docH = linhas.length * tam * 1.36 + tam * 3.4;
|
|
247
|
+
const x = esquerda ? M + 20 * b : W - M - docW - 20 * b;
|
|
248
|
+
const y = Math.max(TOPO, H * 0.52 - docH / 2);
|
|
249
|
+
const [tm] = g([linhas[marca]]);
|
|
250
|
+
els.push({ tipo: 'kit', peca: 'marcatexto', linhas, marca, tMarca: Math.max(0.9, tm), x: r2(x), y: r2(y), largura: r2(docW), tamanho: r2(tam), t: 0.25,
|
|
251
|
+
titulo: p.fonte || 'citação', fonte: F.serifa, fonteMono: F.mono, corAcento: ACENTO, fundo: misturarCor(PAPEL, '#ffffff', 0.55), rotacao: esquerda ? -0.7 : 0.6 });
|
|
252
|
+
if (p.autor) els.push({ tipo: 'kit', peca: 'fonte', texto: '— ' + p.autor, x: r2(esquerda ? x + docW + 50 * b : x - 50 * b), y: r2(y + docH - 40 * b),
|
|
253
|
+
alinhamento: esquerda ? 'left' : 'right', t: Math.min(d - 0.8, Math.max(1.4, tm + 0.8)), tamanho: r2(26 * b), ...base, cor: misturarCor(TINTA, PAPEL, 0.3) });
|
|
254
|
+
origem = [x + docW / 2, y + (marca + 2) * tam * 1.36];
|
|
255
|
+
som(t0 + Math.max(0.9, tm), 'sopro', 0.15);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
case 'imagem': {
|
|
259
|
+
const m = imagens[i] || { src: p.src, tipo: 'imagem' };
|
|
260
|
+
const iw = deitado ? W * 0.5 : W - 2 * M, ih = iw * (deitado ? 0.62 : 0.75);
|
|
261
|
+
const ix = deitado ? (esquerda ? W - M - iw : M) : M, iy = deitado ? Math.max(TOPO, H * 0.52 - ih / 2) : TOPO + 40 * b;
|
|
262
|
+
els.push({ tipo: m.tipo === 'video' ? 'video' : 'imagem', src: m.src, ...(m.tipo === 'video' ? { volume: 0 } : {}), x: r2(ix), y: r2(iy), largura: r2(iw), altura: r2(ih),
|
|
263
|
+
ajuste: 'cover', raio: 4, sombra: '0 1px 1px rgba(20,16,10,0.25), 0 20px 40px -14px rgba(20,16,10,0.45)',
|
|
264
|
+
anima: { rotacao: [k(0, esquerda ? 0.8 : -0.8)], escala: [k(0, 1.0), k(d, 1.025, 'camera')] } });
|
|
265
|
+
if (titulo) {
|
|
266
|
+
const tamP = Math.min(50 * b, (deitado ? W * 0.36 : W - 2 * M) / (titulo.length * 0.72));
|
|
267
|
+
const px = deitado ? (esquerda ? ix - 40 * b - titulo.length * tamP * 0.36 : ix + iw + 40 * b + titulo.length * tamP * 0.36) : W / 2;
|
|
268
|
+
const py = deitado ? iy + ih * 0.78 : iy + ih + 150 * b;
|
|
269
|
+
const pxOk = Math.min(W - M - titulo.length * tamP * 0.4, Math.max(M + titulo.length * tamP * 0.4, px));
|
|
270
|
+
els.push({ tipo: 'kit', peca: 'pilula', texto: titulo, sub: p.sub || '', x: r2(pxOk), y: r2(py), alvo: { x: r2(ix + iw * (esquerda ? 0.3 : 0.7)), y: r2(iy + ih * 0.42) },
|
|
271
|
+
t: g([titulo])[0], tamanho: r2(tamP), ...base, cor: '#ffffff', corTexto: TINTA, corLinha: TINTA });
|
|
272
|
+
}
|
|
273
|
+
if (p.fonte) els.push({ tipo: 'kit', peca: 'fonte', texto: p.fonte, x: r2(ix), y: r2(iy + ih + 24 * b), t: 0.8, tamanho: r2(18 * b), ...base, cor: misturarCor(TINTA, PAPEL, 0.35) });
|
|
274
|
+
origem = [ix + iw / 2, iy + ih / 2];
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
case 'grafico': {
|
|
278
|
+
const barras = (p.barras || itensDe(p).map((x, j) => ({ rotulo: x, valor: itensDe(p).length - j }))).slice(0, 6);
|
|
279
|
+
const gw = deitado ? W * 0.6 : W - 2 * M;
|
|
280
|
+
const tamB = Math.min(44 * b, (H * 0.46) / (barras.length * 2.2));
|
|
281
|
+
const altB = barras.length * tamB * 1.5 * 1.45;
|
|
282
|
+
const y = Math.max(TOPO + 130 * b, H * 0.6 - altB / 2);
|
|
283
|
+
if (titulo) {
|
|
284
|
+
const tt = caber(titulo, { largura: gw, linhas: 2, max: 96 * b, min: 34 * b, fonte: F.titulo }).tamanho;
|
|
285
|
+
els.push({ tipo: 'kit', peca: 'letra', texto: titulo, destaque: tese(titulo, p.destaque), x: xA, y: r2(y - tt * 1.1 * Math.min(2, Math.ceil(titulo.length * tt * 0.56 / gw)) - 40 * b),
|
|
286
|
+
largura: r2(gw), tamanho: tt, t: 0.2, ...base });
|
|
287
|
+
}
|
|
288
|
+
els.push({ tipo: 'kit', peca: 'barras', barras, x: xA, y: r2(y), largura: r2(gw), tamanho: r2(tamB), t: g([String(barras[0].rotulo)])[0] + 0.1, ...base });
|
|
289
|
+
if (p.fonte) fonteTag(p.fonte, y + altB + 20 * b, 1.6);
|
|
290
|
+
origem = [xA + gw / 2, y + altB / 2];
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case 'cta': {
|
|
294
|
+
modo = 'climax';
|
|
295
|
+
const fundo = p.link || marca.nome || titulo;
|
|
296
|
+
els.push({ tipo: 'kit', peca: 'textura', texto: fundo, largura: W * 1.16, altura: H * 1.16, x: -W * 0.08, y: -H * 0.08, tamanho: r2((deitado ? 96 : 84) * b),
|
|
297
|
+
fonte: F.titulo, cor: TINTA, t: 0.05, camada: 1, velocidade: 30 });
|
|
298
|
+
const fit = caber(titulo, { largura: W - 2 * M, linhas: 2, max: 150 * b, min: 44 * b, fonte: F.titulo });
|
|
299
|
+
const alt = fit.linhas * fit.tamanho * 1.02;
|
|
300
|
+
const ts = g(titulo.split(/\s+/).slice(0, 1));
|
|
301
|
+
const alvo = semAcento(tese(titulo, p.destaque));
|
|
302
|
+
els.push({ tipo: 'forma', forma: 'retangulo', x: 0, y: r2(H / 2 - alt / 2 - 50 * b), largura: '100%', altura: r2(alt + 100 * b), cor: PAPEL, opacidade: 0.94, camada: 1 });
|
|
303
|
+
els.push({ tipo: 'kit', peca: 'palavra', x: W / 2, y: r2(H / 2 - alt / 2), largura: W - 2 * M, tamanho: fit.tamanho, alinhamento: 'center', ...base,
|
|
304
|
+
palavras: titulo.split(/\s+/).map((w, j) => ({ texto: w, t: r2(ts[0] + j * 5 / FPS), acento: semAcento(w) === alvo, peso: PEQUENAS.has(semAcento(w)) ? 400 : 700 })) });
|
|
305
|
+
if (p.link) els.push({ tipo: 'kit', peca: 'pilula', texto: p.link, x: W / 2, y: r2(H / 2 + alt / 2 + 110 * b), alvo: { x: W / 2, y: r2(H / 2 + alt / 2 + 20 * b) },
|
|
306
|
+
t: Math.min(d - 0.9, ts[0] + 0.7), tamanho: r2(34 * b), ...base, cor: '#ffffff', corTexto: TINTA, corLinha: TINTA });
|
|
307
|
+
origem = [W / 2, H / 2];
|
|
308
|
+
som(t0 + ts[0], 'toque', 0.35);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case 'logo': {
|
|
312
|
+
escuro = true; modo = 'abertura';
|
|
313
|
+
const nome = p.titulo || marca.nome || '';
|
|
314
|
+
const tam = Math.min(130 * b, (W - 2 * M) / (Math.max(4, nome.length) * 1.06));
|
|
315
|
+
if (marca.logo) els.push({ tipo: 'imagem', src: marca.logo, x: W / 2, y: r2(H / 2 - tam * 1.5), largura: r2(tam * 1.4), altura: r2(tam * 1.4), ancora: 'centro', ajuste: 'contain', de: 0.1 });
|
|
316
|
+
els.push({ tipo: 'kit', peca: 'especime', termo: nome, sub: p.sub || '', x: W / 2, y: r2(H / 2 + (marca.logo ? tam * 0.4 : 0)), t: 0.4, tamanho: r2(tam), ...base, cor: '#f2eee6', corSub: '#9d968a' });
|
|
317
|
+
origem = [W / 2, H / 2];
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case 'cores': {
|
|
321
|
+
const lista = (p.cores || [cores.fundo, cores.destaque, cores.tinta, cores.apoio].filter(ehCor).map((hex, j) => ({ nome: ['Fundo', 'Destaque', 'Tinta', 'Apoio'][j], hex })))
|
|
322
|
+
.slice(0, 5).map((c) => (typeof c === 'string' ? { nome: c, hex: c } : c));
|
|
323
|
+
if (titulo) fonteTag(titulo, TOPO, 0.2);
|
|
324
|
+
els.push({ tipo: 'kit', peca: 'cores', cores: lista.length ? lista : [{ nome: 'Tinta', hex: TINTA }, { nome: 'Papel', hex: PAPEL }, { nome: 'Acento', hex: ACENTO }],
|
|
325
|
+
x: W / 2, y: r2(H * 0.56), largura: r2((deitado ? 300 : 280) * b), raioX: r2(deitado ? W * 0.3 : W * 0.28), raioY: r2(deitado ? H * 0.2 : H * 0.18), t: 0.3, ...base });
|
|
326
|
+
origem = [W / 2, H * 0.56];
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case 'produto': {
|
|
330
|
+
const pw = deitado ? W * 0.62 : W - 2 * M;
|
|
331
|
+
if (titulo) fonteTag(titulo, TOPO - 20 * b, 0.1, { x: r2((W - pw) / 2) });
|
|
332
|
+
els.push({ tipo: 'kit', peca: 'painel', titulo: marca.nome || titulo, texto: p.texto || p.sub || titulo, destaque: p.destaque || tese(p.texto || p.sub || titulo), selo: p.selo,
|
|
333
|
+
barras: p.barras, x: r2((W - pw) / 2), y: r2(Math.max(TOPO, H * 0.5 - pw * 0.22)), largura: r2(pw), t: 0.2, ...base, fonte: F.texto });
|
|
334
|
+
origem = [W / 2, H / 2];
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
default: { // titulo e o que mais vier
|
|
338
|
+
const fit = caber(titulo, { largura: larguraAncora, linhas: 3, max: 160 * b, min: 44 * b, fonte: F.titulo });
|
|
339
|
+
const alt = fit.linhas * fit.tamanho * 1.04;
|
|
340
|
+
const y = Math.max(TOPO + 20 * b, H * 0.7 - alt);
|
|
341
|
+
const alvo = tese(titulo, p.destaque);
|
|
342
|
+
const [tA] = g([alvo]);
|
|
343
|
+
/* Do lado direito o texto encosta na margem direita: alinhado à
|
|
344
|
+
esquerda numa caixa larga, ele flutuaria no meio e a câmera o
|
|
345
|
+
empurraria para fora do quadro. */
|
|
346
|
+
const dir = !esquerda;
|
|
347
|
+
els.push({ tipo: 'kit', peca: 'letra', texto: titulo, destaque: alvo, x: r2(dir ? W - M : xA), y: r2(y), largura: r2(larguraAncora), tamanho: fit.tamanho, t: 0.12,
|
|
348
|
+
tDestaque: Math.max(0.5, tA), alinhamento: dir ? 'right' : 'left', ...base });
|
|
349
|
+
if (p.sub) fonteTag(p.sub, y + alt + 36 * b, Math.min(d - 0.8, Math.max(0.9, tA + 0.5)), dir ? { x: r2(W - M), alinhamento: 'right' } : {});
|
|
350
|
+
// A câmera ancora na margem do texto: o empurrão cresce para dentro, nunca para fora do quadro.
|
|
351
|
+
origem = [dir ? W - M : xA, y + alt / 2];
|
|
352
|
+
som(t0 + Math.max(0.5, tA), 'sopro', 0.14);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const cam = corteInterno(camera(d, modo, i), d, tp, t0);
|
|
357
|
+
return {
|
|
358
|
+
duracao: d,
|
|
359
|
+
fundo: escuro ? ESCURO : PAPEL,
|
|
360
|
+
escuro,
|
|
361
|
+
secao: String(i + 1).padStart(2, '0') + ' — ' + (p.tipo || 'parte'),
|
|
362
|
+
camadas: CAMADAS,
|
|
363
|
+
camera: cam,
|
|
364
|
+
cameraOrigem: origem ? `${r2(origem[0])}px ${r2(origem[1])}px` : 'center center',
|
|
365
|
+
transicao: 'corte',
|
|
366
|
+
elementos: [papel(escuro), ...els],
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const plano = {
|
|
371
|
+
formato: { largura: W, altura: H, fps: FPS },
|
|
372
|
+
fundoGeral: PAPEL,
|
|
373
|
+
estilo: 'vox',
|
|
374
|
+
acabamento: { grao: 0.06, vinheta: 0.22, corVinheta: '40,30,18' },
|
|
375
|
+
cenas,
|
|
376
|
+
audio: {
|
|
377
|
+
...(narracao ? { narracao } : {}),
|
|
378
|
+
...(trilha ? { trilha: { src: trilha, volumes: volumesDaTrilha(temVoz ? tempos : [], total), loop: true } } : {}),
|
|
379
|
+
efeitos,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
return { ok: true, plano, resumo: { cenas: cenas.length, segundos: total, partes: partes.length, fps: FPS, estilo: 'vox' } };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
module.exports = { montarVox, tese, quebrar, termoDef };
|
package/lib/fala.js
CHANGED
|
@@ -41,91 +41,6 @@ const { EventEmitter } = require('events');
|
|
|
41
41
|
const { c, actionHeader, friendlyError } = require('./ui');
|
|
42
42
|
const markdown = require('./markdown.js');
|
|
43
43
|
|
|
44
|
-
/* ── O CANAL ESTRUTURADO ─────────────────────────────────────────────────
|
|
45
|
-
* A janela do app (lib/app.js) mostra a conversa como CHAT: a fala do Primo
|
|
46
|
-
* em texto e cada ação dele numa linha cinza ("Leu index.html", "Rodou npm
|
|
47
|
-
* test ✓"). Adivinhar isso relendo a saída colorida do terminal seria frágil
|
|
48
|
-
* — um rótulo que muda aqui quebraria a janela calado. Então a MESMA função
|
|
49
|
-
* que desenha no terminal também avisa, por este canal, o que desenhou.
|
|
50
|
-
*
|
|
51
|
-
* Sem ouvinte (terminal puro), `avisar` não faz nada: nenhum custo para quem
|
|
52
|
-
* não abriu a janela. */
|
|
53
|
-
const eventos = new EventEmitter();
|
|
54
|
-
eventos.setMaxListeners(20);
|
|
55
|
-
function avisar(tipo, dados) {
|
|
56
|
-
if (!eventos.listenerCount('evento')) return;
|
|
57
|
-
try { eventos.emit('evento', Object.assign({ tipo }, dados || {})); } catch { /* a janela nunca derruba o agente */ }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* O que a linha de ação da janela diz, por ferramenta. Verbo no passado
|
|
61
|
-
porque a linha fica na conversa depois que a ação acabou — "Lendo" viraria
|
|
62
|
-
mentira um segundo depois. O ícone é uma família, não um desenho por tool:
|
|
63
|
-
a página tem dez ícones, não setenta. */
|
|
64
|
-
const VERBOS = {
|
|
65
|
-
read_file: ['Leu', 'ler'], write_file: ['Escreveu', 'escrever'], edit_file: ['Editou', 'editar'],
|
|
66
|
-
multi_edit: ['Editou', 'editar'], list_dir: ['Listou', 'buscar'], glob: ['Procurou', 'buscar'],
|
|
67
|
-
grep: ['Buscou', 'buscar'], run_shell: ['Rodou', 'rodar'], run_in_new_terminal: ['Abriu um terminal com', 'rodar'],
|
|
68
|
-
open_in_browser: ['Abriu', 'web'], spawn_agent: ['Chamou um agente', 'agente'], write_todos: ['Planejou', 'plano'],
|
|
69
|
-
finish: ['Concluiu', 'plano'], pesquisar_referencia: ['Pesquisou', 'web'], remember_general: ['Guardou na memória', 'plano'],
|
|
70
|
-
planejar_producao: ['Planejou a produção', 'agente'], construir_app: ['Construiu', 'agente'],
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/** Verbo, alvo e ícone de uma ação — só o que cabe numa linha, nunca o
|
|
74
|
-
* conteúdo inteiro de um write_file. */
|
|
75
|
-
function resumoDaAcao(name, args) {
|
|
76
|
-
const a = args || {};
|
|
77
|
-
let [verbo, icone] = VERBOS[name] || [];
|
|
78
|
-
if (!verbo) {
|
|
79
|
-
if (/^browser_/.test(name)) { verbo = 'Navegador · ' + name.slice(8); icone = 'web'; }
|
|
80
|
-
else if (/^desktop_/.test(name)) { verbo = 'Computador · ' + name.slice(8).replace(/_/g, ' '); icone = 'computador'; }
|
|
81
|
-
else if (/^estudio_/.test(name)) { verbo = 'Estúdio · ' + name.slice(8); icone = 'agente'; }
|
|
82
|
-
else { verbo = name; icone = 'outro'; }
|
|
83
|
-
}
|
|
84
|
-
let alvo = a.path || a.command || a.pattern || a.consulta || a.url || a.target || a.app || a.selector
|
|
85
|
-
|| a.titulo || a.description || a.arquivo || (a.todos ? `${a.todos.length} tarefas` : '') || '';
|
|
86
|
-
if (typeof alvo !== 'string') alvo = '';
|
|
87
|
-
alvo = alvo.replace(/\s+/g, ' ').trim();
|
|
88
|
-
if (alvo.length > 120) alvo = alvo.slice(0, 119) + '…';
|
|
89
|
-
return { nome: name, verbo, alvo, icone };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** O resultado de uma ação, resumido para a linha da janela: ok ou não, o
|
|
93
|
-
* "+12 −3" das edições e até 40 linhas de detalhe para quem expandir. */
|
|
94
|
-
function resumoDoResultado(name, r) {
|
|
95
|
-
if (!r || typeof r !== 'object') return { nome: name, ok: true };
|
|
96
|
-
if (r.ok === false) {
|
|
97
|
-
return { nome: name, ok: false, resumo: String(r.error || `exit ${r.code ?? 'erro'}`).replace(/\s+/g, ' ').trim().slice(0, 300) };
|
|
98
|
-
}
|
|
99
|
-
const fora = { nome: name, ok: true };
|
|
100
|
-
if (typeof r._antes === 'string' && typeof r._depois === 'string') {
|
|
101
|
-
try {
|
|
102
|
-
const diff = require('./diff.js');
|
|
103
|
-
const n = r._antes.split('\n').length + r._depois.split('\n').length;
|
|
104
|
-
if (n <= diff.MAX_LINHAS_ARQUIVO * 2) {
|
|
105
|
-
const ops = diff.operacoes(r._antes, r._depois);
|
|
106
|
-
Object.assign(fora, diff.contar(ops));
|
|
107
|
-
fora.detalhe = ops.filter((o) => o.tipo !== ' ').slice(0, 40).map((o) => o.tipo + ' ' + o.texto).join('\n');
|
|
108
|
-
}
|
|
109
|
-
} catch { /* sem diff: a linha sai sem os números */ }
|
|
110
|
-
}
|
|
111
|
-
if (name === 'write_file') fora.criado = !!r.created;
|
|
112
|
-
if (name === 'run_shell' || name === 'run_in_new_terminal') {
|
|
113
|
-
const saida = String(r.stdout || '').trim() + (r.stderr ? '\n' + String(r.stderr).trim() : '');
|
|
114
|
-
fora.detalhe = saida.split('\n').slice(-40).join('\n').trim();
|
|
115
|
-
fora.codigo = r.code ?? 0;
|
|
116
|
-
}
|
|
117
|
-
const plural = (n, um, varios) => `${n} ${n === 1 ? um : varios}`;
|
|
118
|
-
if (name === 'read_file') fora.resumo = plural(r.linhas || 0, 'linha', 'linhas');
|
|
119
|
-
if (name === 'grep') fora.resumo = plural(r.total || 0, 'ocorrência', 'ocorrências');
|
|
120
|
-
if (name === 'glob') fora.resumo = plural(r.total || 0, 'arquivo', 'arquivos');
|
|
121
|
-
if (name === 'list_dir' && r.tree) fora.detalhe = String(r.tree).split('\n').slice(0, 40).join('\n');
|
|
122
|
-
if (name === 'write_todos' && Array.isArray(r.todos)) {
|
|
123
|
-
fora.detalhe = r.todos.map((t) => (t.status === 'completed' ? '✓ ' : '○ ') + t.content).join('\n');
|
|
124
|
-
fora.resumo = `${r.feitos}/${r.total}`;
|
|
125
|
-
}
|
|
126
|
-
return fora;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
44
|
/* Até este tamanho, o texto que o modelo escreve antes de chamar uma tool é
|
|
130
45
|
guardado e sai de uma vez, como pensamento. Passou disso sem tool à vista,
|
|
131
46
|
é resposta — e volta a correr na tela conforme chega. Uma frase de
|
|
@@ -144,7 +59,6 @@ function pensamento(texto, profundidade) {
|
|
|
144
59
|
if (!ls.length || !ls[0]) return '';
|
|
145
60
|
const p = ident(profundidade);
|
|
146
61
|
const saida = ls.map((l, i) => `${p} ${i === 0 ? c.dim('✦') : ' '} ${c.muted(l)}`).join('\n');
|
|
147
|
-
avisar('pensamento', { texto: ls.join('\n'), prof: profundidade || 0 });
|
|
148
62
|
console.log(saida);
|
|
149
63
|
return saida;
|
|
150
64
|
}
|
|
@@ -152,7 +66,6 @@ function pensamento(texto, profundidade) {
|
|
|
152
66
|
/** A ação (tool) que ele decidiu fazer. */
|
|
153
67
|
function acao(name, args, profundidade) {
|
|
154
68
|
const saida = ident(profundidade) + actionHeader(name, args);
|
|
155
|
-
avisar('acao', Object.assign(resumoDaAcao(name, args), { prof: profundidade || 0 }));
|
|
156
69
|
console.log(saida);
|
|
157
70
|
return saida;
|
|
158
71
|
}
|
|
@@ -190,7 +103,6 @@ function resposta(texto) {
|
|
|
190
103
|
const largura = Math.max(40, (process.stdout.columns || 80) - 4);
|
|
191
104
|
const linhasMd = markdown.renderizar(texto, largura);
|
|
192
105
|
const saida = '\n' + linhasMd.map((l, i) => (i === 0 ? `${c.brand('●')} ${l}` : ` ${l}`)).join('\n');
|
|
193
|
-
avisar('resposta', { texto: String(texto) });
|
|
194
106
|
console.log(saida);
|
|
195
107
|
return saida;
|
|
196
108
|
}
|
|
@@ -200,7 +112,6 @@ function resposta(texto) {
|
|
|
200
112
|
function aviso(texto) {
|
|
201
113
|
if (!String(texto || '').trim()) return '';
|
|
202
114
|
const saida = '\n' + formatar(texto, c.warn('●'));
|
|
203
|
-
avisar('aviso', { texto: String(texto) });
|
|
204
115
|
console.log(saida);
|
|
205
116
|
return saida;
|
|
206
117
|
}
|
|
@@ -244,11 +155,9 @@ function fluxo() {
|
|
|
244
155
|
escrever(delta) {
|
|
245
156
|
const t = String(delta || '');
|
|
246
157
|
if (!t) return;
|
|
247
|
-
avisar('pedaco', { texto: t });
|
|
248
158
|
sair(desenho.pedaco(t));
|
|
249
159
|
},
|
|
250
160
|
fechar() {
|
|
251
|
-
avisar('fim', {});
|
|
252
161
|
sair(desenho.fim());
|
|
253
162
|
if (escreveu) process.stdout.write('\n');
|
|
254
163
|
},
|
|
@@ -266,7 +175,6 @@ function paralelo(quantos, profundidade) {
|
|
|
266
175
|
/** Um agente delegado começando: "◆ Agente·frontend a folha de estilo". */
|
|
267
176
|
function agente(rotulo, descricao, profundidade) {
|
|
268
177
|
const saida = `${ident(profundidade)} ${c.brand('◆')} ${c.bold(rotulo || 'Agente')}${descricao ? ' ' + c.muted(descricao) : ''}`;
|
|
269
|
-
avisar('acao', { nome: 'agente', verbo: rotulo || 'Agente', alvo: String(descricao || '').slice(0, 120), icone: 'agente', prof: profundidade || 0 });
|
|
270
178
|
console.log(saida);
|
|
271
179
|
return saida;
|
|
272
180
|
}
|
|
@@ -275,9 +183,8 @@ function agente(rotulo, descricao, profundidade) {
|
|
|
275
183
|
function agenteVoltou(resumo, profundidade) {
|
|
276
184
|
const texto = String(resumo || 'sem resultado').replace(/\s+/g, ' ').trim();
|
|
277
185
|
const saida = resultado(profundidade) + c.muted(texto.length > 140 ? texto.slice(0, 139) + '…' : texto);
|
|
278
|
-
avisar('resultado', { nome: 'agente', ok: true, detalhe: texto.slice(0, 2000) });
|
|
279
186
|
console.log(saida);
|
|
280
187
|
return saida;
|
|
281
188
|
}
|
|
282
189
|
|
|
283
|
-
module.exports = {
|
|
190
|
+
module.exports = { LIMIAR_PENSAMENTO, pensamento, acao, resultado, recuo, resposta, aviso, problema, frase, fluxo, paralelo, agente, agenteVoltou };
|