primocode 10.15.2 → 10.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/primocode.js +64 -8
- package/lib/act.js +2 -0
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +264 -0
- package/lib/estudio/index.js +9 -1
- package/lib/estudio/maquina-diretor.js +9 -5
- package/lib/estudio/maquina.js +5 -1
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +93 -1
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +22 -1
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/tools.js +20 -4
- package/lib/ui.js +1 -1
- package/package.json +2 -2
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +460 -3
- package/voz/primo_voz/chat.html +1762 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
|
@@ -220,8 +220,9 @@ class Voz:
|
|
|
220
220
|
self.voz = voz_escolhida
|
|
221
221
|
self.cfg = identidade.ler()
|
|
222
222
|
self.nome = self.cfg["nome"]
|
|
223
|
-
#
|
|
224
|
-
self.
|
|
223
|
+
# Sem palavra de ativação: o microfone só abre no clique do Primo (ptt) e fecha no clique ou ao fim da conversa.
|
|
224
|
+
self._mic = None
|
|
225
|
+
self._mic_aberto = False
|
|
225
226
|
self.local = transcrever.disponivel()
|
|
226
227
|
if self.local:
|
|
227
228
|
transcrever.aquecer()
|
|
@@ -246,7 +247,6 @@ class Voz:
|
|
|
246
247
|
self._falando = False
|
|
247
248
|
self._parar_fala = threading.Event()
|
|
248
249
|
self._acordado_ate = 0.0 # janela de conversa: sem repetir o nome
|
|
249
|
-
self._ptt_ate = 0.0
|
|
250
250
|
self._ultima_conf = 0.0
|
|
251
251
|
self._ultimo_ok = ""
|
|
252
252
|
self.bolinha.enviar({"nome": self.nome})
|
|
@@ -265,9 +265,46 @@ class Voz:
|
|
|
265
265
|
self.sons.tocar(estado, falando=self._falando)
|
|
266
266
|
|
|
267
267
|
def _acao_do_painel(self, acao: str) -> None:
|
|
268
|
-
if acao == "ptt": # clique
|
|
269
|
-
self.
|
|
270
|
-
|
|
268
|
+
if acao == "ptt": # clique no Primo: abre o modo voz; clicar de novo fecha
|
|
269
|
+
if self._mic_aberto:
|
|
270
|
+
self._fechar_mic()
|
|
271
|
+
else:
|
|
272
|
+
self._abrir_mic()
|
|
273
|
+
|
|
274
|
+
def _abrir_mic(self) -> bool:
|
|
275
|
+
if self._mic_aberto or not self._vivo:
|
|
276
|
+
return self._mic_aberto
|
|
277
|
+
mic = escuta.Microfone(self._ao_receber)
|
|
278
|
+
try:
|
|
279
|
+
mic.abrir()
|
|
280
|
+
except Exception as e:
|
|
281
|
+
falar_para_o_node({"tipo": "erro", "texto": f"não consegui abrir o microfone: {e}"})
|
|
282
|
+
self._erro("Não consegui abrir o microfone.")
|
|
283
|
+
return False
|
|
284
|
+
self._mic, self._mic_aberto = mic, True
|
|
285
|
+
self._esvaziar_blocos()
|
|
286
|
+
self.gravador.comecar()
|
|
287
|
+
self._acordado_ate = time.time() + 25
|
|
288
|
+
self._estado("ouvindo")
|
|
289
|
+
self.sons.tocar("acordou", falando=self._falando)
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
def _fechar_mic(self) -> None:
|
|
293
|
+
"""Desliga o microfone e volta a dormir: nada é gravado nem transcrito até o próximo clique."""
|
|
294
|
+
mic, self._mic, self._mic_aberto = self._mic, None, False
|
|
295
|
+
if mic:
|
|
296
|
+
mic.fechar()
|
|
297
|
+
self._acordado_ate = 0.0
|
|
298
|
+
self._pend = ""
|
|
299
|
+
self.gravador.espera = escuta.ESPERA_COMECAR
|
|
300
|
+
self._esvaziar_blocos()
|
|
301
|
+
if not self._falando:
|
|
302
|
+
self._estado("dormindo")
|
|
303
|
+
|
|
304
|
+
def _conversa_acabou(self) -> bool:
|
|
305
|
+
"""O mesmo critério de antes para voltar a dormir: a janela de conversa passou e ninguém está pensando/falando."""
|
|
306
|
+
return (self._mic_aberto and not self._falando and not self._pend
|
|
307
|
+
and self.maquina.estado == "ouvindo" and time.time() > self._acordado_ate)
|
|
271
308
|
|
|
272
309
|
def _erro(self, texto: str) -> None:
|
|
273
310
|
self.luz.erro()
|
|
@@ -277,7 +314,7 @@ class Voz:
|
|
|
277
314
|
# ── o ouvido ─────────────────────────────────────────────────────────
|
|
278
315
|
|
|
279
316
|
def _ao_receber(self, bloco) -> None:
|
|
280
|
-
if not self._vivo:
|
|
317
|
+
if not self._vivo or not self._mic_aberto:
|
|
281
318
|
return
|
|
282
319
|
nivel = escuta.rms(bloco)
|
|
283
320
|
if not self._falando and self.bolinha.ultimo: # o volume REAL do mic vai para as barras da tela
|
|
@@ -343,17 +380,9 @@ class Voz:
|
|
|
343
380
|
threading.Thread(target=trabalho, daemon=True).start()
|
|
344
381
|
|
|
345
382
|
def _ouvir(self) -> None:
|
|
346
|
-
|
|
347
|
-
try:
|
|
348
|
-
mic.abrir()
|
|
349
|
-
except Exception as e:
|
|
350
|
-
falar_para_o_node({"tipo": "erro", "texto": f"não consegui abrir o microfone: {e}"})
|
|
351
|
-
self.parar()
|
|
352
|
-
return
|
|
383
|
+
# O microfone NÃO abre aqui: só no clique (_acao_do_painel). Sem clique, nada grava nem transcreve.
|
|
353
384
|
falar_para_o_node({"tipo": "pronto"})
|
|
354
|
-
self._estado("
|
|
355
|
-
self.sons.tocar("acordou")
|
|
356
|
-
self._saudar()
|
|
385
|
+
self._estado("dormindo")
|
|
357
386
|
while self._vivo:
|
|
358
387
|
self.gravador.comecar()
|
|
359
388
|
acabou = False
|
|
@@ -362,6 +391,8 @@ class Voz:
|
|
|
362
391
|
try:
|
|
363
392
|
bloco = self._blocos.get(timeout=0.3)
|
|
364
393
|
except queue.Empty:
|
|
394
|
+
if self._conversa_acabou():
|
|
395
|
+
self._fechar_mic()
|
|
365
396
|
continue
|
|
366
397
|
acabou = self.gravador.alimentar(bloco)
|
|
367
398
|
if self.gravador._falando and not self._falando:
|
|
@@ -387,7 +418,7 @@ class Voz:
|
|
|
387
418
|
texto = transcrever.limpar(self._transcrever(wav))
|
|
388
419
|
self.lat(f"transcrição {time.time() - self._t_acabou:.2f}s (a frase fechou após {escuta.RABO_VAD if self.gravador.vad else escuta.RABO_SILENCIO:.1f}s de silêncio) -> {texto!r}")
|
|
389
420
|
self._juntar_e_tratar(texto)
|
|
390
|
-
|
|
421
|
+
self._fechar_mic()
|
|
391
422
|
|
|
392
423
|
# "espera": a pessoa segura a vez. Palavras que, sozinhas no fim, prometem continuação.
|
|
393
424
|
_SEGURA = re.compile(r"(?:^|[\s,.])(?:espera|espere|pera ai|perai|aguarda|aguarde|calma|deixa eu pensar)[\s.!?,]*$", re.I)
|
|
@@ -433,26 +464,13 @@ class Voz:
|
|
|
433
464
|
self._tratar(texto)
|
|
434
465
|
|
|
435
466
|
def _tratar(self, texto: str) -> None:
|
|
436
|
-
"""O que foi dito:
|
|
467
|
+
"""O que foi dito (o microfone só está aberto se a pessoa clicou): apresentação, comando local ou pedido ao terminal."""
|
|
437
468
|
if not texto:
|
|
438
469
|
self._estado(self.maquina.estado if self.maquina.estado != "pensando" else "ouvindo")
|
|
439
470
|
return
|
|
440
471
|
agora = time.time()
|
|
441
|
-
acordado = (self.modo == "sempre" or agora < self._acordado_ate or agora < self._ptt_ate)
|
|
442
|
-
chamou, resto = conversa.ativacao(texto, self.nome)
|
|
443
|
-
if not acordado and not chamou:
|
|
444
|
-
self.lat(f"ignorado (sem o nome \"{self.nome}\" e fora da conversa): {texto!r}")
|
|
445
|
-
self.bolinha.enviar({"transcricao": f"(diga \"{self.nome}\" antes do pedido) {texto}", "parcial": False})
|
|
446
|
-
self._estado("dormindo") # não era comigo: não vira pedido
|
|
447
|
-
return
|
|
448
|
-
texto = resto if chamou else texto
|
|
449
472
|
self._acordado_ate = agora + 25
|
|
450
|
-
self.bolinha.enviar({"transcricao": texto
|
|
451
|
-
if not texto: # só chamou pelo nome
|
|
452
|
-
self.sons.tocar("entendeu")
|
|
453
|
-
self._dizer(identidade.variar(["Oi! Fala.", "Pois não?", "Tô aqui."], self._ultimo_ok))
|
|
454
|
-
self.maquina.mandar("ativar")
|
|
455
|
-
return
|
|
473
|
+
self.bolinha.enviar({"transcricao": texto, "parcial": False})
|
|
456
474
|
novo = identidade.aprender(texto)
|
|
457
475
|
if novo:
|
|
458
476
|
self.cfg = identidade.salvar(**novo)
|
|
@@ -495,10 +513,6 @@ class Voz:
|
|
|
495
513
|
self._estado("pensando")
|
|
496
514
|
falar_para_o_node({"tipo": "ouvi", "texto": texto})
|
|
497
515
|
|
|
498
|
-
def _saudar(self) -> None:
|
|
499
|
-
if self.cfg.get("pessoa") or True:
|
|
500
|
-
self._dizer(identidade.saudacao(time.localtime().tm_hour, self.cfg.get("pessoa", ""), self.nome))
|
|
501
|
-
|
|
502
516
|
def _dizer(self, texto: str) -> None:
|
|
503
517
|
"""Fala uma frase curta já pronta (sem passar pelo resumo)."""
|
|
504
518
|
self._ultimo_ok = texto
|
|
@@ -666,8 +680,9 @@ class Voz:
|
|
|
666
680
|
return
|
|
667
681
|
self._falando = False
|
|
668
682
|
self.maquina.mandar("fim_fala")
|
|
669
|
-
self._estado("ouvindo" if self.
|
|
670
|
-
|
|
683
|
+
self._estado("ouvindo" if self._mic_aberto else "dormindo")
|
|
684
|
+
if self._mic_aberto:
|
|
685
|
+
self._acordado_ate = max(self._acordado_ate, time.time() + 90) # conversa em andamento: 90 s até voltar a dormir
|
|
671
686
|
self.sons.tocar("pronto")
|
|
672
687
|
self.corte.nivel_dele = 0.0
|
|
673
688
|
self._esvaziar_blocos() # o que o microfone pegou era a voz dele
|
|
@@ -763,7 +778,7 @@ class Voz:
|
|
|
763
778
|
self._erro("Deu ruim. O detalhe está no terminal.")
|
|
764
779
|
est = "ouvindo"
|
|
765
780
|
self.maquina.mandar("sem_fala")
|
|
766
|
-
self._estado("ouvindo" if est == "esperando" and
|
|
781
|
+
self._estado("ouvindo" if est == "esperando" and self._mic_aberto else
|
|
767
782
|
("dormindo" if est == "esperando" else est))
|
|
768
783
|
if est == "esperando":
|
|
769
784
|
self.sons.tocar("pronto", falando=self._falando)
|