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
+28 -3
View File
@@ -45,6 +45,7 @@ from vida import Vida
from criacao import Criacao
from inspiracao import Inspiracao
from aprendizado import Aprendizado
from console import Console
TRAVA = threading.Lock()
@@ -73,12 +74,13 @@ ARQUIVO_APARENCIA = "aparencia.json"
# Arquivos servidos como estão: o rosto do celular e o rastreamento do painel.
ESTATICOS = {"/carinha.html", "/camera.html", "/reacoes-render.js", "/rastreador-worker.js", "/rosto-motor.js", "/voz-robo.js",
"/vendor/mediapipe/vision_bundle.mjs", "/vendor/mediapipe/face.tflite", "/vendor/mediapipe/hands.task",
"/vendor/mediapipe/expressao.task",
"/vendor/mediapipe/wasm/vision_wasm_internal.js", "/vendor/mediapipe/wasm/vision_wasm_internal.wasm",
"/vendor/mediapipe/wasm/vision_wasm_nosimd_internal.js", "/vendor/mediapipe/wasm/vision_wasm_nosimd_internal.wasm"}
# Rotas que não exigem token: só o necessário para a página do rosto abrir e o app se achar.
PUBLICAS_GET = {"/app/health", "/faces", "/gestos", "/config", "/ia/modelo", "/app.apk"}
PREFIXOS_API = ("/eventos", "/estado", "/fila", "/rtc/", "/conversa/", "/treinamento/", "/historico/", "/vida/", "/aprendizado/", "/criacao/",
PREFIXOS_API = ("/eventos", "/console", "/estado", "/fila", "/rtc/", "/conversa/", "/treinamento/", "/historico/", "/vida/", "/aprendizado/", "/criacao/",
"/app/", "/braco/", "/servos/", "/ia/", "/api/", "/gesto/", "/face/", "/diag/")
@@ -122,10 +124,26 @@ EVENTOS = Eventos()
ESTOP = {"ativo": False, "desde": None}
CONSOLE = Console()
# ordens que se repetem (o modo vida reenvia o humor a cada minuto): no console, só em "detalhes"
ORDENS_DETALHE = ("humor ", "ritmo ", "zanga ", "sono ")
def enfileirar(ordem):
"""Caminho único de uma ordem para o rosto: fila do celular + evento para os painéis."""
FILA.adicionar(ordem)
EVENTOS.publicar("expressao", {"ordem": ordem})
if ordem.startswith("dizer "):
CONSOLE.registrar("celular", "Falou: " + ordem[6:])
elif ordem.startswith(("atividade_criada ", "expressao_criada ")):
try:
nome = json.loads(ordem.split(" ", 1)[1]).get("nome", "?")
except ValueError:
nome = "?"
CONSOLE.registrar("celular", "Mandou ao rosto a criação: " + nome)
elif not ordem.startswith("aprendi "):
CONSOLE.registrar("celular", "Mandou ao rosto: " + ordem,
nivel="detalhe" if ordem.startswith(ORDENS_DETALHE) else "info")
def carregar_aparencia():
@@ -264,7 +282,7 @@ class Handler(SimpleHTTPRequestHandler):
def modulos(self):
s = self.server
return [getattr(s, nome) for nome in ("api_config", "historico", "vida", "aprendizado", "inspiracao", "criacao", "treinamento", "conversa", "cerebro",
return [getattr(s, nome) for nome in ("console", "api_config", "historico", "vida", "aprendizado", "inspiracao", "criacao", "treinamento", "conversa", "cerebro",
"braco", "servos", "controle") if hasattr(s, nome)]
def estado(self):
@@ -486,7 +504,8 @@ class Servidor(ThreadingHTTPServer):
def ligar_eventos(servidor):
for nome in ("controle", "servos", "cerebro", "conversa", "treinamento", "historico", "vida", "criacao", "inspiracao", "aprendizado"):
for nome in ("controle", "servos", "cerebro", "conversa", "treinamento", "historico", "vida", "criacao", "inspiracao", "aprendizado",
"console"):
if hasattr(servidor, nome):
getattr(servidor, nome).publicar = EVENTOS.publicar
@@ -560,6 +579,12 @@ def main():
def nome_criada(ident):
return next((a['nome'] for a in servidor.criacao.atividades() if a['id'] == ident), None)
servidor.aprendizado = aprendizado = Aprendizado(Path.cwd() / 'aprendizado.json', nome_criada)
# Console: tudo o que ele faz e por quê (cada parte registra o motivo da decisão)
servidor.console = CONSOLE
for parte in (vida, aprendizado, inspiracao, servidor.cerebro, servidor.historico, servidor.controle):
parte.console = CONSOLE.registrar
vida.dia_anterior = lambda: aprendizado.historico[-1] if aprendizado.historico else None
CONSOLE.registrar("sistema", "Servidor iniciado", "versão " + versao())
aprendizado.anotar = lambda tipo, texto, agora=None: (vida.anotar(tipo, texto, agora), vida.avisar())
vida.aprendizado = aprendizado
# a análise da semana usa a mesma IA do estúdio (Gemini, IA local ou o S10)