feat(rosto): motor de palpebras, 4 rostos novos, formato do olho e tela Rosto

- rosto-motor.js: emocoes como posicao de palpebra (cima, inclinacao, baixo)
  com transicao suave; respiracao, sacadas e piscada que achata o olho
- rostos novos Carinha Viva, Bloco, Matriz (LEDs) e Lente (iris/pupila);
  os classicos continuam; emoji/figurinha nos olhos seguem no SVG
- formato do olho (padrao, redondo, pilula, quadrado, largo) para Viva,
  Bloco e Matriz; salvo na aparencia e enviado como ordem "formato X"
- toque na tela do S10: cutucar = susto e risada, deslizar = carinho,
  tocar dormindo acorda
- painel: tela Rosto (celular em destaque, cartoes de rosto, formatos,
  cores, testar emocoes so na previa ou no celular, lista do que ja esta
  pronto); Ao vivo leva a ela; paleta ganha rostos e formatos
- corrige /comando rosto que nao salvava a aparencia

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
julio
2026-10-06 01:33:56 -03:00
co-authored by Claude Opus 5.5
parent b6b8e2efd5
commit 972dd8dd66
18 changed files with 1054 additions and 95 deletions
+10
View File
@@ -121,6 +121,16 @@ def testar(base, server, capturas):
page.get_by_label('Buscar ajuste').fill('')
page.screenshot(path=str(Path(capturas) / 'ajustes.png'), full_page=True)
# 6a. rosto: escolher estilo e formato do olho
page.goto(base + '/#/rosto')
page.get_by_role('radio', name='Bloco').click()
page.get_by_role('radio', name='Quadrado', exact=True).click()
page.wait_for_timeout(500)
assert kiosk.APARENCIA['rosto'] == 'bloco' and kiosk.APARENCIA['formato'] == 'quadrado', kiosk.APARENCIA
expect(page.get_by_text('BLOCO · QUADRADO')).to_be_visible()
if capturas:
page.screenshot(path=str(Path(capturas) / 'rosto.png'), full_page=True)
# 6b. conversa: pergunta de hora é respondida pelo relógio (sem IA) e chega por SSE
page.get_by_role('link', name='Conversa').first.click()
page.get_by_label('Mensagem').fill('Que horas são?')
+11
View File
@@ -109,6 +109,17 @@ class ServidorTest(unittest.TestCase):
self.assertEqual(self.pedir('GET', '/fila?aparelho=1')[1], ['feliz', 'rosto miudo'])
self.assertEqual(self.pedir('GET', '/fila?aparelho=1')[1], [])
def test_rostos_novos_e_formato_do_olho(self):
kiosk.ARQUIVO_APARENCIA = str(Path(self.tmp.name) / 'aparencia.json')
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'rosto', 'valor': 'bloco'})[0], 200)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'formato', 'valor': 'quadrado'})[0], 200)
self.assertEqual(self.pedir('POST', '/comando', {'tipo': 'formato', 'valor': 'triangulo'})[0], 400)
self.assertEqual(kiosk.FILA.ordens(), ['rosto bloco', 'formato quadrado'])
estado = self.pedir('GET', '/estado')[1]
self.assertEqual(estado['aparencia']['formato'], 'quadrado')
self.assertEqual(estado['aparencia']['rosto'], 'bloco') # a escolha fica salva
self.assertIn('pilula', estado['formatos'])
def test_post_face_usa_prefixo_rosto(self):
conn = http.client.HTTPConnection('127.0.0.1', self.porta, timeout=5)
conn.request('POST', '/face', body='miudo', headers={'Authorization': 'Bearer ' + TOKEN})