feat(vida): modo autônomo, o Carinha como bichinho virtual
Novo módulo vida.py: energia, alegria, companhia e diversão caem com o tempo e sobem com presença (PIR), movimento, conversa e carinho. Delas sai o humor e, do humor, os atos: gestos, cochilo quando cansado, dormir à noite sem ninguém por perto, acenar pedindo atenção quando carente com gente perto, tocar reações da biblioteca e inventar figurinhas novas com o Gemini (no máximo 3 por dia, 90 min entre elas). - O celular recebe "humor x" e escolhe os gestos soltos por ele. - Toque na tela do celular conta como carinho/cutucada (/vida/cuidar). - Respeita E-Stop e o modo presença (ninguém em casa = quieto). - Painel: cartão Vida no Ao vivo com humor, barras, cuidados, ritmo e diário; liga por padrão. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
import { ErroApi, pedir, quandoNegado, urlEventos } from './api';
|
||||
import { avisos } from './avisos.svelte';
|
||||
import type { AppConfig, Conversa, EstadoServidor, IA, Motores, Reacao, Telemetria } from './tipos';
|
||||
import type { AppConfig, Conversa, EstadoServidor, IA, Motores, Reacao, Telemetria, Vida } from './tipos';
|
||||
|
||||
export type Conexao = 'conectando' | 'aberta' | 'caiu' | 'token';
|
||||
|
||||
@@ -33,6 +33,7 @@ class Estado {
|
||||
reacao = $state<Reacao | null>(null);
|
||||
treinamento = $state({ ocupado: false, erro: '' });
|
||||
api = $state<EstadoServidor['api'] | null>(null);
|
||||
vida = $state<Vida | null>(null);
|
||||
feed = $state<ItemFeed[]>([]);
|
||||
/** Sensor PIR da placa: último disparo (s, epoch) e se está ligado. */
|
||||
pir = $state<{ disponivel: boolean; ultimo: number | null; ativo?: boolean; presenca?: { estado: 'presente' | 'ausente'; desde: number; ultimo: number } } | null>(null);
|
||||
@@ -108,12 +109,14 @@ function aplicarFoto(f: EstadoServidor) {
|
||||
estado.reacao = f.reacao ?? null;
|
||||
if (f.treinamento) estado.treinamento = f.treinamento;
|
||||
if (f.api) estado.api = f.api;
|
||||
if (f.vida) estado.vida = f.vida;
|
||||
if (f.pir) estado.pir = { disponivel: f.pir.disponivel, ultimo: f.pir.ultimo, ativo: (f.pir as any).ativo, presenca: (f.pir as any).presenca };
|
||||
}
|
||||
|
||||
const tratadores: Record<string, (d: any) => void> = {
|
||||
expressao: (d: { ordem: string }) => {
|
||||
if (d.ordem !== 'calma') noFeed('expressao', 'Expressão: ' + d.ordem);
|
||||
// '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);
|
||||
},
|
||||
telemetria: (d: Telemetria) => {
|
||||
estado.app = d;
|
||||
@@ -142,6 +145,11 @@ const tratadores: Record<string, (d: any) => void> = {
|
||||
if (d?.receita?.nome) noFeed('reacao', 'Reação: ' + d.receita.nome);
|
||||
},
|
||||
treinamento: (d) => (estado.treinamento = d),
|
||||
vida: (d: Vida) => {
|
||||
const novo = d.diario[0];
|
||||
if (novo && novo.horario !== estado.vida?.diario[0]?.horario && novo.tipo !== 'acao') noFeed('vida', d.emoji + ' ' + novo.texto);
|
||||
estado.vida = d;
|
||||
},
|
||||
pir: (d: { ultimo: number; ativo?: boolean; presenca?: any }) => (estado.pir = { disponivel: true, ultimo: d.ultimo, ativo: d.ativo, presenca: d.presenca }),
|
||||
historico: (d) => {
|
||||
const n = d?.novo;
|
||||
@@ -169,7 +177,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']) {
|
||||
'reacao', 'treinamento', 'historico', 'pir', 'vida']) {
|
||||
fonte.addEventListener(tipo, (e) => {
|
||||
const dados = JSON.parse((e as MessageEvent).data);
|
||||
tratadores[tipo]?.(dados);
|
||||
|
||||
Reference in New Issue
Block a user