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.
@@ -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
- # nome = só responde se começar pelo nome · sempre = sem palavra · tecla = só ao clicar no painel
224
- self.modo = os.environ.get("PRIMOCODE_ATIVACAO", "nome")
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 nos anéis = falar agora
269
- self._ptt_ate = time.time() + 10
270
- self.sons.tocar("acordou", falando=self._falando)
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
- mic = escuta.Microfone(self._ao_receber)
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("ouvindo" if self.modo == "sempre" else "dormindo")
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
- mic.fechar()
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: ativação, apresentação, comando local ou pedido ao terminal."""
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 or f"({self.nome}?)", "parcial": False})
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.modo == "sempre" or time.time() < self._acordado_ate else "dormindo")
670
- self._acordado_ate = max(self._acordado_ate, time.time() + 90) # conversa em andamento: 90 s sem repetir o nome
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 time.time() < self._acordado_ate else
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)