primocode 10.4.7 → 10.6.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 +2 -2
- package/bin/primocode.js +117 -24
- package/lib/act.js +141 -9
- package/lib/agents-md.js +140 -0
- package/lib/auditoria.js +122 -0
- package/lib/browser.js +48 -1
- package/lib/catalogo.js +29 -5
- package/lib/comandos-extra.js +2 -0
- package/lib/config.js +32 -4
- package/lib/conta.js +9 -3
- package/lib/entrada.js +33 -11
- package/lib/estudio/colecao.js +218 -0
- package/lib/estudio/dados/fluent-3d.json +1 -0
- package/lib/estudio/dados/noto-animado.json +1 -0
- package/lib/estudio/diretor.js +1 -0
- package/lib/estudio/estilo.js +6 -2
- package/lib/estudio/falantes.js +30 -6
- package/lib/estudio/gerativo.js +8 -0
- package/lib/estudio/hibrido.js +144 -8
- package/lib/estudio/index.js +202 -83
- package/lib/estudio/kit/animacao.jsx +41 -0
- package/lib/estudio/kit/gerativo.jsx +21 -1
- package/lib/estudio/kit/index.jsx +3 -1
- package/lib/estudio/kit/mundo.jsx +1060 -0
- package/lib/estudio/kit/voxel.jsx +854 -0
- package/lib/estudio/kit.js +5 -1
- package/lib/estudio/kokoro.js +18 -4
- package/lib/estudio/margem.js +1 -1
- package/lib/estudio/mundo.js +305 -0
- package/lib/estudio/personagem.js +234 -10
- package/lib/estudio/producao.js +107 -17
- package/lib/estudio/projeto.js +77 -22
- package/lib/estudio/remotion.js +2 -0
- package/lib/leitura.js +176 -0
- package/lib/local/cerebro.json +180 -3
- package/lib/local/entrada.js +61 -4
- package/lib/mcp.js +296 -12
- package/lib/memoria-conversa.js +32 -3
- package/lib/mencoes.js +5 -1
- package/lib/nome-curto.js +126 -0
- package/lib/nome-projeto.js +5 -2
- package/lib/outra-pasta.js +161 -0
- package/lib/perigo.js +34 -2
- package/lib/referencia.js +40 -11
- package/lib/repeticao.js +20 -1
- package/lib/tela.js +1 -1
- package/lib/tools.js +76 -6
- package/lib/travado.js +141 -0
- package/lib/ui.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* colecao.js — a biblioteca completa do Estúdio: o que existe para pôr num vídeo.
|
|
3
|
+
*
|
|
4
|
+
* "tem que ter uma biblioteca completa de ícones, pra ele poder pegar os
|
|
5
|
+
* ícones 3D, 2D, animações, também efeitos sonoros e músicas, também
|
|
6
|
+
* fontes de texto" (Joel, 11/10)
|
|
7
|
+
*
|
|
8
|
+
* O 2D (Iconify), a imagem e o som (Openverse), a trilha e as fontes já
|
|
9
|
+
* existiam (assets.js, biblioteca.js, sons-leves.js). Faltavam duas coisas, e
|
|
10
|
+
* as duas entram aqui:
|
|
11
|
+
*
|
|
12
|
+
* · ÍCONE 3D — o Fluent Emoji 3D da Microsoft (licença MIT): mais de 1.500 objetos
|
|
13
|
+
* renderizados em 3D, de "coffee" a "rocket". O índice mora no pacote
|
|
14
|
+
* (dados/fluent-3d.json), então PROCURAR não usa rede; só o PNG escolhido
|
|
15
|
+
* desce, do CDN do jsDelivr.
|
|
16
|
+
* · ANIMAÇÃO PRONTA — o Noto Emoji animado do Google (CC BY 4.0): 881
|
|
17
|
+
* animações em Lottie (vetor, nítido em qualquer tamanho), tocadas quadro
|
|
18
|
+
* a quadro pela peça kit/animacao.jsx. Índice também no pacote.
|
|
19
|
+
*
|
|
20
|
+
* E `cardapio()` junta TUDO numa resposta só, para a IA saber o que existe
|
|
21
|
+
* antes de escolher — a escolha é dela (nada entra no vídeo sozinho).
|
|
22
|
+
*
|
|
23
|
+
* A licença anda junto: cada download grava o .json ao lado (fonte, licença,
|
|
24
|
+
* crédito), como no acervo.js.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const fs = require('fs');
|
|
30
|
+
const path = require('path');
|
|
31
|
+
|
|
32
|
+
const DADOS = path.join(__dirname, 'dados');
|
|
33
|
+
const CDN_FLUENT = 'https://cdn.jsdelivr.net/gh/microsoft/fluentui-emoji@main/assets/';
|
|
34
|
+
const LOTTIE_NOTO = (cp) => `https://fonts.gstatic.com/s/e/notoemoji/latest/${cp}/lottie.json`;
|
|
35
|
+
const GIF_NOTO = (cp) => `https://fonts.gstatic.com/s/e/notoemoji/latest/${cp}/512.gif`;
|
|
36
|
+
|
|
37
|
+
let _fluent = null, _noto = null;
|
|
38
|
+
const fluent = () => (_fluent = _fluent || JSON.parse(fs.readFileSync(path.join(DADOS, 'fluent-3d.json'), 'utf8')));
|
|
39
|
+
const noto = () => (_noto = _noto || JSON.parse(fs.readFileSync(path.join(DADOS, 'noto-animado.json'), 'utf8')));
|
|
40
|
+
|
|
41
|
+
/* As duas bibliotecas são em inglês. A IA costuma mandar o termo em inglês,
|
|
42
|
+
mas a pessoa fala português: as palavras mais comuns de vídeo têm par. */
|
|
43
|
+
const PT_EN = {
|
|
44
|
+
cafe: 'coffee', 'café': 'coffee', pao: 'bread', 'pão': 'bread', padaria: 'bread', dinheiro: 'money', grana: 'money', moeda: 'coin',
|
|
45
|
+
foguete: 'rocket', coracao: 'heart', 'coração': 'heart', fogo: 'fire', ideia: 'light bulb', lampada: 'light bulb', 'lâmpada': 'light bulb',
|
|
46
|
+
estrela: 'star', relogio: 'clock', 'relógio': 'clock', tempo: 'hourglass', celular: 'mobile phone', telefone: 'telephone',
|
|
47
|
+
computador: 'laptop', notebook: 'laptop', robo: 'robot', 'robô': 'robot', livro: 'books', escola: 'school', carro: 'car',
|
|
48
|
+
casa: 'house', comida: 'food', pizza: 'pizza', musica: 'musical note', 'música': 'musical note', foto: 'camera', camera: 'camera',
|
|
49
|
+
'câmera': 'camera', mundo: 'globe', terra: 'globe', chave: 'key', cadeado: 'locked', seguranca: 'shield', 'segurança': 'shield',
|
|
50
|
+
escudo: 'shield', grafico: 'chart', 'gráfico': 'chart', alvo: 'direct hit', trofeu: 'trophy', 'troféu': 'trophy', medalha: 'medal',
|
|
51
|
+
presente: 'gift', festa: 'party', sol: 'sun', chuva: 'rain', raio: 'high voltage', energia: 'high voltage', bateria: 'battery',
|
|
52
|
+
email: 'envelope', mensagem: 'speech balloon', conversa: 'speech balloon', pergunta: 'question', ok: 'check mark', certo: 'check mark',
|
|
53
|
+
errado: 'cross mark', aviso: 'warning', dinossauro: 'dinosaur', gato: 'cat', cachorro: 'dog', planta: 'seedling', arvore: 'tree',
|
|
54
|
+
'árvore': 'tree', sorriso: 'smile', feliz: 'smile', triste: 'sad', bravo: 'angry', amor: 'heart', rindo: 'joy', pensando: 'thinking',
|
|
55
|
+
surpreso: 'surprised', cerebro: 'brain', 'cérebro': 'brain', olho: 'eye', mao: 'hand', 'mão': 'hand', joinha: 'thumbs up',
|
|
56
|
+
palmas: 'clapping', aplauso: 'clapping', calendario: 'calendar', 'calendário': 'calendar', saco: 'money bag', banco: 'bank',
|
|
57
|
+
cartao: 'credit card', 'cartão': 'credit card', loja: 'shopping', compras: 'shopping cart', voto: 'ballot box', ferramenta: 'hammer',
|
|
58
|
+
// Comida, objeto e veículo: o que vira personagem ou peça da cena 3D (mundo.js).
|
|
59
|
+
hamburguer: 'hamburger', 'hambúrguer': 'hamburger', batata: 'potato', 'batata frita': 'french fries', sorvete: 'ice cream', bolo: 'birthday cake',
|
|
60
|
+
chocolate: 'chocolate bar', maca: 'red apple', 'maçã': 'red apple', uva: 'grapes', morango: 'strawberry', abacaxi: 'pineapple', melancia: 'watermelon',
|
|
61
|
+
cenoura: 'carrot', ovo: 'egg', queijo: 'cheese wedge', leite: 'glass of milk', cerveja: 'beer mug', vinho: 'wine glass', suco: 'beverage box',
|
|
62
|
+
refrigerante: 'cup with straw', sanduiche: 'sandwich', 'sanduíche': 'sandwich', sushi: 'sushi', 'cachorro-quente': 'hot dog', pipoca: 'popcorn',
|
|
63
|
+
rosquinha: 'doughnut', donut: 'doughnut', biscoito: 'cookie', abacate: 'avocado', coco: 'coconut', taco: 'taco', croissant: 'croissant',
|
|
64
|
+
barraca: 'tent', fogueira: 'fire', palmeira: 'palm tree', violao: 'guitar', 'violão': 'guitar', guitarra: 'guitar', bola: 'soccer ball',
|
|
65
|
+
bicicleta: 'bicycle', moto: 'motorcycle', aviao: 'airplane', 'avião': 'airplane', trem: 'locomotive', onibus: 'bus', 'ônibus': 'bus',
|
|
66
|
+
navio: 'ship', helicoptero: 'helicopter', 'helicóptero': 'helicopter', lapis: 'pencil', 'lápis': 'pencil', caneta: 'pen', mochila: 'backpack',
|
|
67
|
+
chapeu: 'top hat', 'chapéu': 'top hat', oculos: 'glasses', 'óculos': 'glasses', televisao: 'television', 'televisão': 'television', tv: 'television',
|
|
68
|
+
microfone: 'microphone', fone: 'headphone', 'guarda-chuva': 'umbrella', cogumelo: 'mushroom', semaforo: 'vertical traffic light',
|
|
69
|
+
'semáforo': 'vertical traffic light', ampulheta: 'hourglass', lua: 'crescent moon', nuvem: 'cloud', arco_iris: 'rainbow', 'arco-íris': 'rainbow',
|
|
70
|
+
flor: 'blossom', rosa: 'rose', girassol: 'sunflower', 'cacto': 'cactus', vulcao: 'volcano', 'vulcão': 'volcano', castelo: 'castle',
|
|
71
|
+
fantasma: 'ghost', alien: 'alien', 'alienígena': 'alien', et: 'alien', unicornio: 'unicorn', 'unicórnio': 'unicorn', dragao: 'dragon', 'dragão': 'dragon',
|
|
72
|
+
// Bichos que o kit/mundo.jsx não monta de blocos: vêm do ícone 3D (a lula fica de fora: é o presidente).
|
|
73
|
+
polvo: 'octopus', baleia: 'whale', caranguejo: 'crab', borboleta: 'butterfly', abelha: 'honeybee', cobra: 'snake', jacare: 'crocodile',
|
|
74
|
+
'jacaré': 'crocodile', crocodilo: 'crocodile', formiga: 'ant', aranha: 'spider', camelo: 'camel', zebra: 'zebra', hipopotamo: 'hippopotamus',
|
|
75
|
+
'hipopótamo': 'hippopotamus', rinoceronte: 'rhinoceros', canguru: 'kangaroo', coala: 'koala', preguica: 'sloth', 'preguiça': 'sloth',
|
|
76
|
+
joaninha: 'lady beetle', caracol: 'snail', minhoca: 'worm', morcego: 'bat', esquilo: 'chipmunk', rato: 'mouse', hamster: 'hamster',
|
|
77
|
+
lagosta: 'lobster', camarao: 'shrimp', 'camarão': 'shrimp', flamingo: 'flamingo', pavao: 'peacock', 'pavão': 'peacock', cisne: 'swan',
|
|
78
|
+
aguia: 'eagle', 'águia': 'eagle', peru: 'turkey', lhama: 'llama', gorila: 'gorilla', orangotango: 'orangutan', bufalo: 'water buffalo',
|
|
79
|
+
engrenagem: 'gear', ciencia: 'test tube', 'ciência': 'test tube', saude: 'hospital', 'saúde': 'hospital', remedio: 'pill', 'remédio': 'pill',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const norm = (t) => String(t || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^a-z0-9 ]+/g, ' ').trim();
|
|
83
|
+
|
|
84
|
+
function termos(termo) {
|
|
85
|
+
const cru = String(termo || '').toLowerCase().trim();
|
|
86
|
+
const out = new Set([norm(cru)]);
|
|
87
|
+
for (const w of cru.split(/\s+/)) if (PT_EN[w]) out.add(PT_EN[w]);
|
|
88
|
+
if (PT_EN[cru]) out.add(PT_EN[cru]);
|
|
89
|
+
return [...out].filter(Boolean);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A nota de um nome contra a busca: palavra inteira vale mais que pedaço.
|
|
93
|
+
function nota(nome, ts) {
|
|
94
|
+
const n = ' ' + norm(nome) + ' ';
|
|
95
|
+
let melhor = 0;
|
|
96
|
+
for (const t of ts) {
|
|
97
|
+
if (n.trim() === t) melhor = Math.max(melhor, 100);
|
|
98
|
+
else if (n.includes(' ' + t + ' ')) melhor = Math.max(melhor, 60 - n.length * 0.1);
|
|
99
|
+
else {
|
|
100
|
+
const ws = t.split(' ').filter((w) => w.length > 2);
|
|
101
|
+
const achou = ws.filter((w) => n.includes(' ' + w)).length;
|
|
102
|
+
if (achou) melhor = Math.max(melhor, (achou / ws.length) * 40 - n.length * 0.1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return melhor;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Os ícones 3D (Fluent) que casam com o termo. */
|
|
109
|
+
function procurarIcones3d(termo, { quantas = 10 } = {}) {
|
|
110
|
+
const ts = termos(termo);
|
|
111
|
+
if (!ts.length) return { ok: false, error: 'diga o que procurar (termo), de preferência em inglês: "coffee", "rocket".' };
|
|
112
|
+
// O nome manda; as palavras-chave do metadata (coffee em "hot beverage") e o grupo ajudam.
|
|
113
|
+
const achados = fluent().map(([nome, p, kw = '', grupo = '']) => ({ nome, p, grupo, n: Math.max(nota(nome, ts), nota(kw, ts) * 0.8, nota(grupo, ts) * 0.3) })).filter((x) => x.n > 0)
|
|
114
|
+
.sort((a, b) => b.n - a.n).slice(0, quantas);
|
|
115
|
+
return {
|
|
116
|
+
ok: true, termo, total: achados.length,
|
|
117
|
+
icones: achados.map((x) => ({ id: 'fluent3d:' + x.nome, nome: x.nome, grupo: x.grupo, url: CDN_FLUENT + encodeURI(x.p), licenca: 'MIT', fonte: 'Microsoft Fluent Emoji 3D' })),
|
|
118
|
+
comoUsar: 'baixe com acao:icones3d id:"fluent3d:<nome>" (precisa do id do projeto) — volta um elemento {tipo:"imagem"} pronto, PNG transparente de 256 px em 3D.',
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** As animações prontas (Noto animado, Lottie) que casam com o termo. */
|
|
123
|
+
function procurarAnimacoes(termo, { quantas = 10 } = {}) {
|
|
124
|
+
const ts = termos(termo);
|
|
125
|
+
if (!ts.length) return { ok: false, error: 'diga o que procurar (termo): "fire", "party", "thinking"…' };
|
|
126
|
+
const achados = noto().map(([cp, tags, cat]) => ({ cp, tags, cat, n: Math.max(nota(tags, ts), nota(cat, ts) * 0.5) }))
|
|
127
|
+
.filter((x) => x.n > 0).sort((a, b) => b.n - a.n).slice(0, quantas);
|
|
128
|
+
return {
|
|
129
|
+
ok: true, termo, total: achados.length,
|
|
130
|
+
animacoes: achados.map((x) => ({ id: 'noto:' + x.cp, nome: x.tags, categoria: x.cat, lottie: LOTTIE_NOTO(x.cp), gif: GIF_NOTO(x.cp),
|
|
131
|
+
licenca: 'CC BY 4.0', creditos: 'Noto Emoji Animation, Google (CC BY 4.0)' })),
|
|
132
|
+
comoUsar: 'baixe com acao:animacoes id:"noto:<código>" (precisa do id do projeto) — volta um elemento {tipo:"kit", peca:"animacao"} pronto, que toca quadro a quadro.',
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function baixarUrl(url, destino) {
|
|
137
|
+
const r = await fetch(url, { headers: { 'user-agent': 'primocode-estudio' } });
|
|
138
|
+
if (!r.ok) return { ok: false, error: `${r.status} ao baixar ${url}` };
|
|
139
|
+
const buf = Buffer.from(await r.arrayBuffer());
|
|
140
|
+
fs.mkdirSync(path.dirname(destino), { recursive: true });
|
|
141
|
+
fs.writeFileSync(destino, buf);
|
|
142
|
+
return { ok: true, bytes: buf.length };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Baixa um item da biblioteca para a pasta do projeto e devolve o elemento pronto.
|
|
147
|
+
* @param {string} id "fluent3d:<nome>" ou "noto:<código>"
|
|
148
|
+
*/
|
|
149
|
+
async function baixar(id, { pasta, largura = 1920, altura = 1080 } = {}) {
|
|
150
|
+
if (!pasta) return { ok: false, error: 'para baixar é preciso dizer em qual projeto (id).' };
|
|
151
|
+
const [tipo, chave0] = String(id || '').split(/:(.+)/);
|
|
152
|
+
const chave = String(chave0 || '').trim().toLowerCase();
|
|
153
|
+
const lado = Math.round(Math.min(largura, altura) * 0.32);
|
|
154
|
+
if (tipo === 'fluent3d') {
|
|
155
|
+
const item = fluent().find(([nome]) => nome === chave);
|
|
156
|
+
if (!item) return { ok: false, error: `não há o ícone 3D "${chave}" — procure com acao:icones3d termo:"…".` };
|
|
157
|
+
const arquivo = chave.replace(/[^a-z0-9]+/g, '-') + '.png';
|
|
158
|
+
const destino = path.join(pasta, 'midia', 'icones3d', arquivo);
|
|
159
|
+
if (!fs.existsSync(destino)) {
|
|
160
|
+
const r = await baixarUrl(CDN_FLUENT + encodeURI(item[1]), destino);
|
|
161
|
+
if (!r.ok) return r;
|
|
162
|
+
fs.writeFileSync(destino.replace(/\.png$/, '.json'), JSON.stringify({ fonte: 'Microsoft Fluent Emoji 3D', licenca: 'MIT', url: CDN_FLUENT + encodeURI(item[1]) }, null, 2));
|
|
163
|
+
}
|
|
164
|
+
const src = path.relative(pasta, destino).replace(/\\/g, '/');
|
|
165
|
+
return { ok: true, src, licenca: 'MIT',
|
|
166
|
+
elemento: { tipo: 'imagem', src, x: Math.round((largura - lado) / 2), y: Math.round((altura - lado) / 2), largura: lado, altura: lado, ajuste: 'contain' } };
|
|
167
|
+
}
|
|
168
|
+
if (tipo === 'noto') {
|
|
169
|
+
const item = noto().find(([cp]) => cp === chave);
|
|
170
|
+
if (!item) return { ok: false, error: `não há a animação "${chave}" — procure com acao:animacoes termo:"…".` };
|
|
171
|
+
const destino = path.join(pasta, 'midia', 'animacoes', chave + '.json');
|
|
172
|
+
if (!fs.existsSync(destino)) {
|
|
173
|
+
const r = await baixarUrl(LOTTIE_NOTO(chave), destino);
|
|
174
|
+
if (!r.ok) return r;
|
|
175
|
+
fs.writeFileSync(destino.replace(/\.json$/, '.licenca.json'), JSON.stringify({ fonte: 'Noto Emoji Animation (Google)', licenca: 'CC BY 4.0', url: LOTTIE_NOTO(chave) }, null, 2));
|
|
176
|
+
}
|
|
177
|
+
const src = path.relative(pasta, destino).replace(/\\/g, '/');
|
|
178
|
+
return { ok: true, src, licenca: 'CC BY 4.0', creditos: 'Noto Emoji Animation, Google (CC BY 4.0)',
|
|
179
|
+
elemento: { tipo: 'kit', peca: 'animacao', src, loop: true, x: Math.round((largura - lado) / 2), y: Math.round((altura - lado) / 2), largura: lado, altura: lado } };
|
|
180
|
+
}
|
|
181
|
+
return { ok: false, error: 'id da biblioteca é "fluent3d:<nome>" ou "noto:<código>".' };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* O CARDÁPIO: tudo o que a biblioteca tem, numa resposta. A IA lê isto e
|
|
186
|
+
* escolhe pelo assunto do vídeo — a regra da casa é que todo elemento tenha a
|
|
187
|
+
* ver com o que a fala diz.
|
|
188
|
+
*/
|
|
189
|
+
function cardapio() {
|
|
190
|
+
let fontes = 0, sons = [];
|
|
191
|
+
try { fontes = Object.keys(require('./biblioteca.js').FONTES || {}).length; } catch { /* sem a lista */ }
|
|
192
|
+
try { sons = Object.keys(require('./sons-leves.js').EFEITOS || {}); } catch { /* sem a lista */ }
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
icones2d: { quantos: '200 mil+ (Iconify: Material, Lucide, Phosphor, marcas…)', acao: 'icones', exemplo: 'termo:"bread"' },
|
|
196
|
+
icones3d: { quantos: fluent().length, fonte: 'Microsoft Fluent Emoji 3D (MIT)', acao: 'icones3d', exemplo: 'termo:"coffee" → id:"fluent3d:hot beverage"' },
|
|
197
|
+
animacoes: { quantas: noto().length, fonte: 'Noto Emoji animado do Google, Lottie (CC BY 4.0)', acao: 'animacoes', exemplo: 'termo:"fire" → id:"noto:1f525"' },
|
|
198
|
+
personagens3d: 'o estilo personagem (voxel): bonecos de blocos e o mascote, com a câmera de cinema — estudio_roteiro estilo:"personagem"',
|
|
199
|
+
efeitosSonoros: { sintetizados: sons, internet: 'acao:efeito termo:"whoosh" (Openverse, licença aberta, até 6 s)' },
|
|
200
|
+
musicas: { internet: 'acao:trilha termo:"lofi" (Openverse, 30 s ou mais)', automatica: 'sem trilha pedida, o PrimoCode escolhe uma sem repetir as últimas' },
|
|
201
|
+
fontes: { quantas: fontes || 128, acao: 'fontes', nota: 'Google Fonts, por tipo (condensada, display, serifada, manuscrita…)' },
|
|
202
|
+
imagens: 'acao:imagem termo:"…" (Openverse e Wikimedia; Google com chave) — só quando a pessoa pediu imagem',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** O ícone 3D que É a coisa (nota de palavra inteira), ou null: "pizza" acha, "joel" não. */
|
|
207
|
+
function melhorIcone3d(termo, piso = 80) {
|
|
208
|
+
const ts = termos(termo);
|
|
209
|
+
if (!ts.length) return null;
|
|
210
|
+
let melhor = null;
|
|
211
|
+
for (const [nome, , kw = ''] of fluent()) {
|
|
212
|
+
const n = Math.max(nota(nome, ts), nota(kw, ts) * 0.8);
|
|
213
|
+
if (n >= piso && (!melhor || n > melhor.n || (n === melhor.n && nome.length < melhor.nome.length))) melhor = { nome, n };
|
|
214
|
+
}
|
|
215
|
+
return melhor ? 'fluent3d:' + melhor.nome : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { melhorIcone3d, procurarIcones3d, procurarAnimacoes, baixar, cardapio, termos, PT_EN };
|