feat(vida): jeito de gente e console com o porquê de cada decisão

Console (console.py + tela Console no painel):
- cada parte registra o que fez e o motivo: modo vida (com necessidades
  e humor), sentidos, celular (ordens e o que o rosto fez), IA local,
  aprendizado e ideias do dia; últimas 400 linhas, ao vivo pelo SSE;
- tela com o "agora" (humor, necessidades, o que está fazendo, próxima
  decisão, última decisão, jeito), filtros, detalhes, pausa e busca.

Jeito de gente:
- saudade: a festa cresce com o tempo fora (aceno, "Oi! Você voltou!",
  "Senti sua falta!"); bom dia lembrando de ontem;
- pedidos com motivo ("Brinca comigo?", "Me faz um carinho?", mostrar a
  figurinha nova), no máximo 1 a cada 20 min;
- larga atividade no meio quando fica chato; personalidade (curiosidade,
  brincadeira, timidez) que pesa nos gestos e amadurece com o convívio;
- no rosto: acordar grogue, sonhar com algo do dia (balão no motor),
  hesitar antes de gestos, errar na atividade e ficar sem graça, olhar
  nos olhos e desviar, sorrir de volta (FaceLandmarker no rastreador do
  painel) e reagir à luz (sensor de luz no app 0.10.0);
- ordem "dizer" com legenda e voz de robô.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-11 03:37:07 -03:00
co-authored by Claude Opus 5.5
parent a99aa897c4
commit 2933407b9e
27 changed files with 1248 additions and 33 deletions
+19
View File
@@ -105,6 +105,8 @@ class ControlePC:
self.rtc.encerrar()
return novo.copy()
console = staticmethod(lambda origem, texto, motivo='', nivel='info', agora=None: None) # o main troca pelo console do painel
def resumo(self, host=''):
"""Estado do celular para o painel (GET /app/status, /estado e evento 'telemetria')."""
with self.lock:
@@ -160,12 +162,14 @@ class ControlePC:
if not isinstance(dados, dict):
raise ValueError('Estado deve ser objeto JSON')
with self.lock:
antes = self.estado
self.estado = dados
self.celular_ip = ip_informado(dados) or h.client_address[0]
if self.recarga["id"] and str(dados.get("tela_atualizada", "")) == self.recarga["id"]:
self.recarga["confirmada"] = True
self.status_at = time.monotonic()
self.publicar('telemetria', self.resumo(h.headers.get("Host", "").split(":")[0]))
self.mudancas(antes, dados)
enviar({'ok': True})
else:
enviar(self.resumo(h.headers.get("Host", "").split(":")[0]))
@@ -176,6 +180,21 @@ class ControlePC:
enviar({'erro': str(e)}, 503)
return True
def mudancas(self, antes, agora):
"""O que mudou no celular desde o último status: vai para o console do painel."""
if not antes:
self.console('celular', 'Celular conectado', 'rosto %s' % agora.get('rosto', '?'))
return
if bool(antes.get('dormindo')) != bool(agora.get('dormindo')):
self.console('celular', 'O rosto dormiu' if agora.get('dormindo') else 'O rosto acordou')
if antes.get('atividade') != agora.get('atividade'):
if agora.get('atividade'):
self.console('celular', 'Começou a atividade: %s' % agora['atividade'])
else:
self.console('celular', 'Terminou a atividade: %s' % antes['atividade'])
if bool(antes.get('tela_apagada')) != bool(agora.get('tela_apagada')):
self.console('celular', 'Apagou a tela (dormindo há um tempo)' if agora.get('tela_apagada') else 'Acendeu a tela')
def registrar_recusa(self, rota, motivo):
"""Pedido do celular recusado: aparece no journal (no máximo 1 linha por minuto)."""
agora = time.monotonic()