feat(criacao): estúdio para criar expressões, atividades e figurinhas com IA
Menu "Criar com IA" (Ao vivo › Gestos e Reações) com grade igual à dos favoritos. Para cada pedido escolhe-se o tipo (expressão, atividade ou figurinha) e a IA: Gemini, IA local (qualquer servidor compatível com OpenAI na rede, como o ROG Ally com LM Studio/llama-server) ou o Qwen do S10. - A IA não escreve código: responde uma receita de formato fixo (schema JSON) montada só com peças que o rosto já anima. O servidor valida, limita números e descarta peças desconhecidas antes de chegar ao celular. - Reaproveita o que existe: o prompt leva exemplos reais dos gestos e atividades atuais, e "Basear em" manda a receita de um gesto, atividade ou criação para a IA fazer uma variação. - Expressões viram sequências como as de sequencias(), com mãos e espiral. - Atividades usam uma cena genérica com o bonequinho (poses, pernas, pulo, balanço) e os objetos das cenas feitas à mão (bola, livro, xícara, notas, estrelas, coração, Zzz, chama, bolha, halter, microfone, controle, pincel, flor), com movimentos pular/girar/subir/orbitar. - Figurinhas reaproveitam o desenho e a biblioteca de reações. - A estrela põe expressões e atividades criadas nos favoritos (e nas teclas 1 a 9); a IA local é configurada na própria tela, com teste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+26
-2
@@ -540,11 +540,31 @@ function passo(dt) { // avanca a fila; true se ha gesto em
|
||||
/* Atividade em andamento (rosto-motor.js): para onde olhar e que cara fazer. Atualizado por quadro. */
|
||||
let ATIV = null;
|
||||
const OLHAR = { x: 0, y: 0, ax: 0, ay: 0, prox: 0 }; // ponto que os olhos estão fitando no repouso
|
||||
function atividade(nome, segundos) {
|
||||
function atividade(nome, segundos, receita) {
|
||||
if (!window.MotorRosto || !window.MotorRosto.atividade) return;
|
||||
if (nome === "parar" || !nome) { window.MotorRosto.atividade(null); return; }
|
||||
if (r.dormindo) { r.dormindo = false; animar("surpreso"); }
|
||||
if (window.MotorRosto.atividade(nome, segundos) && nome === "cantar") tocarSom("musica");
|
||||
if (window.MotorRosto.atividade(nome, segundos, receita) && nome === "cantar") tocarSom("musica");
|
||||
}
|
||||
|
||||
/* Expressão criada no estúdio (criacao.py): receita já validada no servidor, só números e nomes de
|
||||
peças. Vira uma sequência igual às de sequencias(), com mãos e espiral se a receita pedir. */
|
||||
function corDaReceita(nome) {
|
||||
return { claro: "claro", escuro: "escuro", rosa: ROSA, amarelo: AMARELO, verde: VERDE, vermelho: VERM }[nome] || CIANO;
|
||||
}
|
||||
function tocarExpressaoCriada(texto) {
|
||||
let rc;
|
||||
try { rc = JSON.parse(texto); } catch (e) { return; }
|
||||
if (!rc || !Array.isArray(rc.passos) || !rc.passos.length) return;
|
||||
if (r.dormindo) r.dormindo = false;
|
||||
r.fila = rc.passos.slice(0, 12).map((p) => [{
|
||||
forma: p.forma || "aberto", forma2: null, boca: p.boca || "neutra", cor: corDaReceita(p.cor),
|
||||
dx: +p.dx || 0, dy: +p.dy || 0, esc: +p.esc || 1, ab: typeof p.ab === "number" ? p.ab : 1,
|
||||
}, Math.max(0.05, Math.min(2, +p.duracao || 0.3))]);
|
||||
proximo();
|
||||
const total = r.fila.reduce((a, passo) => a + passo[1], 0);
|
||||
if (rc.espiral) ESPIRAL.ate = performance.now() / 1000 + total;
|
||||
if (rc.maos && rc.maos !== "nenhuma" && window.MotorRosto && window.MotorRosto.maos) window.MotorRosto.maos(rc.maos);
|
||||
}
|
||||
|
||||
function repouso(t) { // pose calma: olha devagar, "respira"
|
||||
@@ -689,6 +709,10 @@ function trata(ordem) {
|
||||
HUMOR = ordem.slice(6).trim();
|
||||
} else if (ordem.indexOf("ritmo ") === 0) {
|
||||
PERFIL = PERFIS[ordem.slice(6).trim()] || PERFIS.normal;
|
||||
} else if (ordem.indexOf("expressao_criada ") === 0) {
|
||||
tocarExpressaoCriada(ordem.slice(17));
|
||||
} else if (ordem.indexOf("atividade_criada ") === 0) {
|
||||
try { const rc = JSON.parse(ordem.slice(17)); atividade("receita", +rc.duracao || 25, rc); } catch (e) {}
|
||||
} else if (ordem.indexOf("atividade ") === 0) {
|
||||
const [nome, seg] = ordem.slice(10).trim().split(/\s+/); // "atividade bola 180"
|
||||
atividade(nome, parseFloat(seg) || 0);
|
||||
|
||||
Reference in New Issue
Block a user