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:
julio
2026-10-06 22:22:52 -03:00
co-authored by Claude Opus 5.5
parent ff06f02705
commit 05e82dfd75
19 changed files with 1522 additions and 32 deletions
+8 -2
View File
@@ -116,7 +116,13 @@ function aplicarFoto(f: EstadoServidor) {
const tratadores: Record<string, (d: any) => void> = {
expressao: (d: { ordem: string }) => {
// 'humor x' é o modo vida avisando o celular; aparece no cartão Vida, não no feed
if (d.ordem !== 'calma' && !d.ordem.startsWith('humor ')) noFeed('expressao', 'Expressão: ' + d.ordem);
if (d.ordem === 'calma' || /^(humor|ritmo|zanga|sono) /.test(d.ordem)) return;
const criada = /^(expressao|atividade)_criada (.*)$/.exec(d.ordem);
if (criada) {
let nome = '';
try { nome = JSON.parse(criada[2]).nome; } catch { /* ok */ }
noFeed('expressao', (criada[1] === 'expressao' ? 'Expressão criada: ' : 'Atividade criada: ') + nome);
} else noFeed('expressao', 'Expressão: ' + d.ordem);
},
telemetria: (d: Telemetria) => {
estado.app = d;
@@ -177,7 +183,7 @@ function abrirEventos() {
if (estado.conexao !== 'token') estado.conexao = 'caiu';
};
for (const tipo of ['expressao', 'telemetria', 'app_config', 'aparencia', 'motores', 'estop', 'conversa', 'ia',
'reacao', 'treinamento', 'historico', 'pir', 'vida']) {
'reacao', 'treinamento', 'historico', 'pir', 'vida', 'criacao']) {
fonte.addEventListener(tipo, (e) => {
const dados = JSON.parse((e as MessageEvent).data);
tratadores[tipo]?.(dados);