feat(vida): aprendizado com o dia a dia (gosto, rotina da casa e sono)

O Carinha passa a melhorar as interações com o que vive em casa, sem IA
e sem internet (aprendizado.py):
- gosto: gesto ou atividade feito com alguém por perto é uma tentativa;
  carinho, brincadeira ou conversa até 60 s depois contam como "gostaram"
  (cutucada vale meio). A chance de cada ato vai de 0,5x a 2x, relativa à
  média, e só depois de 3 tentativas;
- rotina: fração de minutos com gente em cada hora (média móvel entre
  dias). Antes de uma hora movimentada, acorda do cochilo 15 min antes;
  sono pesado e madrugada continuam respeitados;
- sono: PIR acordou e ninguém ficou nem interagiu em 3 min = à toa. Se
  repete na mesma hora, ali pede 1 ou 2 disparos a mais para acordar;
- à meia-noite fecha o dia: lições em primeira pessoa no diário e o
  passado pesa 15% menos a cada dia (o gosto muda);
- rotas /aprendizado/status|config|esquecer e SSE 'aprendizado';
- painel: cartão "O que ele aprendeu" no Ao vivo (rotina em 24 barras,
  faz mais / faz menos, lições, esquecer com confirmação) e tópico no
  guia.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-09 10:13:23 -03:00
co-authored by Claude Opus 5.5
parent 4777ceb023
commit 24625453f7
12 changed files with 1000 additions and 9 deletions
+5 -2
View File
@@ -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, Vida } from './tipos';
import type { Aprendizado, AppConfig, Conversa, EstadoServidor, IA, Motores, Reacao, Telemetria, Vida } from './tipos';
export type Conexao = 'conectando' | 'aberta' | 'caiu' | 'token';
@@ -34,6 +34,7 @@ class Estado {
treinamento = $state({ ocupado: false, erro: '' });
api = $state<EstadoServidor['api'] | null>(null);
vida = $state<Vida | null>(null);
aprendizado = $state<Aprendizado | 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);
@@ -110,6 +111,7 @@ function aplicarFoto(f: EstadoServidor) {
if (f.treinamento) estado.treinamento = f.treinamento;
if (f.api) estado.api = f.api;
if (f.vida) estado.vida = f.vida;
if (f.aprendizado) estado.aprendizado = f.aprendizado;
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 };
}
@@ -158,6 +160,7 @@ const tratadores: Record<string, (d: any) => void> = {
if (novo && novo.horario !== estado.vida?.diario[0]?.horario && novo.tipo !== 'acao') noFeed('vida', d.emoji + ' ' + novo.texto);
estado.vida = d;
},
aprendizado: (d: Aprendizado) => (estado.aprendizado = 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;
@@ -185,7 +188,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', 'criacao', 'inspiracao']) {
'reacao', 'treinamento', 'historico', 'pir', 'vida', 'aprendizado', 'criacao', 'inspiracao']) {
fonte.addEventListener(tipo, (e) => {
const dados = JSON.parse((e as MessageEvent).data);
tratadores[tipo]?.(dados);