primocode 10.16.0 → 10.17.1

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.
Files changed (237) hide show
  1. package/LICENSES/edit-ia-pro.txt +32 -0
  2. package/LICENSES/fontes-OFL.txt +476 -0
  3. package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
  4. package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
  5. package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
  6. package/LICENSES/three-MIT.txt +23 -0
  7. package/LICENSES/video-use-MIT.txt +24 -0
  8. package/LICENSES/woven-sfx-CC0.txt +16 -0
  9. package/LICENSES/yunet-MIT.txt +21 -0
  10. package/README.md +2 -2
  11. package/bin/primocode.js +28 -8
  12. package/editor/LICENSE.md +11 -0
  13. package/editor/README.md +321 -0
  14. package/editor/SKILL.md +307 -0
  15. package/editor/THIRD_PARTY_NOTICES.md +228 -0
  16. package/editor/assets/baixar.json +591 -0
  17. package/editor/assets/fonts/anton/OFL.txt +93 -0
  18. package/editor/assets/fonts/caveat/OFL.txt +93 -0
  19. package/editor/assets/fonts/figtree/OFL.txt +93 -0
  20. package/editor/assets/fonts/fonts.json +10 -0
  21. package/editor/assets/fonts/montserrat/OFL.txt +93 -0
  22. package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
  23. package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
  24. package/editor/assets/models/YUNET_LICENSE.txt +21 -0
  25. package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
  26. package/editor/assets/sfx/licencas/cash.txt +76 -0
  27. package/editor/assets/sfx/licencas/charge.txt +31 -0
  28. package/editor/assets/sfx/licencas/click.txt +46 -0
  29. package/editor/assets/sfx/licencas/coin.txt +50 -0
  30. package/editor/assets/sfx/licencas/count.txt +47 -0
  31. package/editor/assets/sfx/licencas/data.txt +31 -0
  32. package/editor/assets/sfx/licencas/error.txt +32 -0
  33. package/editor/assets/sfx/licencas/flash.txt +31 -0
  34. package/editor/assets/sfx/licencas/gears.txt +82 -0
  35. package/editor/assets/sfx/licencas/keyboard.txt +48 -0
  36. package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
  37. package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
  38. package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
  39. package/editor/assets/sfx/licencas/notification.txt +54 -0
  40. package/editor/assets/sfx/licencas/pop.txt +38 -0
  41. package/editor/assets/sfx/licencas/rewind.txt +32 -0
  42. package/editor/assets/sfx/licencas/rise.txt +46 -0
  43. package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
  44. package/editor/assets/sfx/licencas/snap.txt +50 -0
  45. package/editor/assets/sfx/licencas/success.txt +40 -0
  46. package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
  47. package/editor/assets/sfx/licencas/tick.txt +14 -0
  48. package/editor/assets/sfx/licencas/whoosh.txt +47 -0
  49. package/editor/assets/sfx/licencas/wind.txt +31 -0
  50. package/editor/assets/sfx/sfx.json +832 -0
  51. package/editor/assets/sfx/sfx_sinteticos.json +317 -0
  52. package/editor/assets/sfx/sfx_woven.json +387 -0
  53. package/editor/assets/sfx/woven/LICENCA.md +16 -0
  54. package/editor/fichas/briefing-do-video.md +45 -0
  55. package/editor/fichas/conferencia-da-amostra.md +56 -0
  56. package/editor/fichas/matriz-de-ganchos.csv +3 -0
  57. package/editor/fichas/padrao.json +16 -0
  58. package/editor/fichas/planilha-de-cenas.csv +4 -0
  59. package/editor/fichas/planilha-de-versoes.csv +4 -0
  60. package/editor/fichas/plano-em-blocos.md +32 -0
  61. package/editor/fichas/registro-de-materiais.json +27 -0
  62. package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
  63. package/editor/helpers/bordas_precisas.py +272 -0
  64. package/editor/helpers/camera_viva.py +426 -0
  65. package/editor/helpers/captions.py +601 -0
  66. package/editor/helpers/common.py +308 -0
  67. package/editor/helpers/compose_final.py +3555 -0
  68. package/editor/helpers/conferir_corte.py +234 -0
  69. package/editor/helpers/conferir_dados.py +405 -0
  70. package/editor/helpers/contact_sheet.py +282 -0
  71. package/editor/helpers/corte_por_onda.py +433 -0
  72. package/editor/helpers/corte_seco.py +595 -0
  73. package/editor/helpers/denoise.py +971 -0
  74. package/editor/helpers/efeitos.py +408 -0
  75. package/editor/helpers/efeitos_gerar.py +159 -0
  76. package/editor/helpers/elevenlabs_api.py +442 -0
  77. package/editor/helpers/face_layout.py +570 -0
  78. package/editor/helpers/fetch_images.py +423 -0
  79. package/editor/helpers/ganho_por_trecho.py +214 -0
  80. package/editor/helpers/gen_image.py +312 -0
  81. package/editor/helpers/gen_music.py +316 -0
  82. package/editor/helpers/gen_sfx.py +148 -0
  83. package/editor/helpers/grade.py +307 -0
  84. package/editor/helpers/icloud.py +119 -0
  85. package/editor/helpers/make_sfx.py +940 -0
  86. package/editor/helpers/matte.py +1342 -0
  87. package/editor/helpers/medir_referencia.py +2232 -0
  88. package/editor/helpers/mixar_trilha.py +354 -0
  89. package/editor/helpers/montar_corte.py +295 -0
  90. package/editor/helpers/motion/__init__.py +13 -0
  91. package/editor/helpers/motion/engine.py +1915 -0
  92. package/editor/helpers/motion/registry.py +510 -0
  93. package/editor/helpers/motion/render.py +665 -0
  94. package/editor/helpers/motion/templates/barra_progresso.py +370 -0
  95. package/editor/helpers/motion/templates/carimbo.py +132 -0
  96. package/editor/helpers/motion/templates/checklist.py +271 -0
  97. package/editor/helpers/motion/templates/comentario_cta.py +380 -0
  98. package/editor/helpers/motion/templates/contador.py +222 -0
  99. package/editor/helpers/motion/templates/destaque.py +263 -0
  100. package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
  101. package/editor/helpers/motion/templates/grafico_barras.py +510 -0
  102. package/editor/helpers/motion/templates/grafico_linha.py +462 -0
  103. package/editor/helpers/motion/templates/notificacao.py +266 -0
  104. package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
  105. package/editor/helpers/motion/templates/passos.py +321 -0
  106. package/editor/helpers/motion/templates/riscado.py +298 -0
  107. package/editor/helpers/motion/templates/selo_garantia.py +150 -0
  108. package/editor/helpers/motion_render.py +241 -0
  109. package/editor/helpers/motion_sugerir.py +961 -0
  110. package/editor/helpers/noise_check.py +134 -0
  111. package/editor/helpers/pack_transcripts.py +223 -0
  112. package/editor/helpers/palavras_na_onda.py +176 -0
  113. package/editor/helpers/plano_sfx.py +839 -0
  114. package/editor/helpers/portao.py +427 -0
  115. package/editor/helpers/preview_server.py +1177 -0
  116. package/editor/helpers/rastrear_rosto.py +298 -0
  117. package/editor/helpers/render_cut.py +1256 -0
  118. package/editor/helpers/respiros.py +89 -0
  119. package/editor/helpers/sincronizar_cameras.py +571 -0
  120. package/editor/helpers/speech_regions.py +423 -0
  121. package/editor/helpers/style_catalog.json +231 -0
  122. package/editor/helpers/style_samples.py +246 -0
  123. package/editor/helpers/tela_verde.py +2113 -0
  124. package/editor/helpers/text_render.py +2059 -0
  125. package/editor/helpers/texto_na_tela.py +366 -0
  126. package/editor/helpers/timeline_view.py +292 -0
  127. package/editor/helpers/tira_de_quadros.py +113 -0
  128. package/editor/helpers/transcribe.py +734 -0
  129. package/editor/helpers/transcribe_batch.py +129 -0
  130. package/editor/helpers/trilhas.py +1146 -0
  131. package/editor/helpers/trilhas_humores.json +66 -0
  132. package/editor/helpers/valores.py +187 -0
  133. package/editor/helpers/verify_cut.py +820 -0
  134. package/editor/helpers/voice_levels.py +233 -0
  135. package/editor/helpers/voz_check.py +386 -0
  136. package/editor/helpers/voz_limpa.py +81 -0
  137. package/editor/helpers/watch_preview.py +372 -0
  138. package/editor/modelo-projeto/LEIA.md +402 -0
  139. package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
  140. package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
  141. package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
  142. package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
  143. package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
  144. package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
  145. package/editor/modelo-projeto/assets/three/LICENSE +21 -0
  146. package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
  147. package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
  148. package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
  149. package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
  150. package/editor/modelo-projeto/build.py +4828 -0
  151. package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
  152. package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
  153. package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
  154. package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
  155. package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
  156. package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
  157. package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
  158. package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
  159. package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
  160. package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
  161. package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
  162. package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
  163. package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
  164. package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
  165. package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
  166. package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
  167. package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
  168. package/editor/modelo-projeto/hyperframes.json +12 -0
  169. package/editor/modelo-projeto/package.json +11 -0
  170. package/editor/modelo-projeto/projeto.json +16 -0
  171. package/editor/preview/app.css +1009 -0
  172. package/editor/preview/app.js +2122 -0
  173. package/editor/preview/index.html +307 -0
  174. package/editor/pyproject.toml +23 -0
  175. package/editor/references/3d.md +236 -0
  176. package/editor/references/animacao-catalogo.md +672 -0
  177. package/editor/references/animacao.md +498 -0
  178. package/editor/references/comandos.md +92 -0
  179. package/editor/references/corte-seco-como-funciona.md +50 -0
  180. package/editor/references/corte-seco.md +88 -0
  181. package/editor/references/corte.md +290 -0
  182. package/editor/references/direcao-por-blocos.md +218 -0
  183. package/editor/references/enquadramento.md +654 -0
  184. package/editor/references/estilos-prontos.md +355 -0
  185. package/editor/references/estudio.md +100 -0
  186. package/editor/references/exemplo-eventos.json +67 -0
  187. package/editor/references/lettering.md +252 -0
  188. package/editor/references/modelos.md +494 -0
  189. package/editor/references/phase2.md +1007 -0
  190. package/editor/references/referencia-aplicar.md +203 -0
  191. package/editor/references/referencia-medir.md +375 -0
  192. package/editor/references/revisao.md +335 -0
  193. package/editor/references/som-e-trilha.md +146 -0
  194. package/editor/references/tabela-de-sons.md +130 -0
  195. package/editor/references/variacoes.md +234 -0
  196. package/editor/uv.lock +1141 -0
  197. package/lib/act.js +36 -8
  198. package/lib/catalogo.js +4 -0
  199. package/lib/chamada-torta.js +81 -0
  200. package/lib/editor-pro.js +786 -0
  201. package/lib/entrega.js +58 -0
  202. package/lib/estudio/conferencia-visual.js +76 -0
  203. package/lib/estudio/diretor.js +6 -1
  204. package/lib/estudio/exportar.js +3 -1
  205. package/lib/estudio/gosto.js +45 -4
  206. package/lib/estudio/index.js +8 -5
  207. package/lib/estudio/kit-ae/base.jsx +119 -13
  208. package/lib/estudio/kit-ae/cenas.jsx +47 -15
  209. package/lib/estudio/kit-ae/guarda.jsx +274 -0
  210. package/lib/estudio/kit-ae/primitivas.jsx +180 -24
  211. package/lib/estudio/maquina-diretor.js +61 -7
  212. package/lib/estudio/maquina-foto.js +177 -0
  213. package/lib/estudio/maquina-guarda.js +207 -0
  214. package/lib/estudio/maquina-layout.js +22 -1
  215. package/lib/estudio/maquina.js +60 -11
  216. package/lib/estudio/referencias.js +1 -1
  217. package/lib/estudio/visao/visao.swift +97 -0
  218. package/lib/estudio/visao.js +60 -0
  219. package/lib/local/cerebro.json +192 -6
  220. package/lib/local/motor.js +5 -2
  221. package/lib/mcp.js +105 -7
  222. package/lib/nuvem.js +11 -1
  223. package/lib/outra-pasta.js +1 -1
  224. package/lib/passagem.js +50 -0
  225. package/lib/perigo.js +29 -12
  226. package/lib/protecao.js +134 -0
  227. package/lib/sandbox.js +11 -4
  228. package/lib/titulo-conversa.js +46 -0
  229. package/lib/tools.js +3 -0
  230. package/lib/ui.js +1 -1
  231. package/package.json +3 -2
  232. package/skills/editar-meu-video/SKILL.md +6 -2
  233. package/skills/editar-video/SKILL.md +113 -0
  234. package/studio/pessoa.py +52 -0
  235. package/voz/primo_voz/app.py +43 -4
  236. package/voz/primo_voz/chat.html +17 -6
  237. package/voz/primo_voz/painel_dados.py +134 -0
package/studio/pessoa.py CHANGED
@@ -175,6 +175,42 @@ def recortar(entrada, saida, suavizar=7, encolher=2, limiar=0.6):
175
175
  "largura": largura, "altura": altura, "fps": round(fps, 2)}
176
176
 
177
177
 
178
+ def recortar_foto(entrada, saida, suavizar=7, encolher=2, limiar=0.6, minimo=0.08):
179
+ """Recorta a pessoa de UMA FOTO e grava um PNG com alfa (foto na cena do motion).
180
+
181
+ Se a pessoa ocupa menos de `minimo` do quadro (ou não há pessoa), devolve
182
+ semPessoa: quem chamou usa a foto inteira com moldura arredondada.
183
+ """
184
+ import cv2
185
+ import numpy as np
186
+ import mediapipe as mp
187
+
188
+ quadro = cv2.imread(entrada, cv2.IMREAD_COLOR)
189
+ if quadro is None:
190
+ return {"ok": False, "error": f"não consegui abrir a foto {entrada}"}
191
+ segmentador = mp.solutions.selfie_segmentation.SelfieSegmentation(model_selection=0)
192
+ try:
193
+ resultado = segmentador.process(cv2.cvtColor(quadro, cv2.COLOR_BGR2RGB))
194
+ finally:
195
+ segmentador.close()
196
+ mascara = (resultado.segmentation_mask > limiar).astype(np.uint8) * 255
197
+ cobertura = float((mascara > 0).mean())
198
+ if cobertura < minimo:
199
+ return {"ok": False, "semPessoa": True, "cobertura": round(cobertura, 3),
200
+ "error": "não achei uma pessoa nesta foto."}
201
+ if encolher > 0:
202
+ mascara = cv2.erode(mascara, np.ones((3, 3), np.uint8), iterations=int(encolher))
203
+ if suavizar > 0:
204
+ k = int(suavizar) | 1
205
+ mascara = cv2.GaussianBlur(mascara, (k, k), 0)
206
+ bgra = cv2.cvtColor(quadro, cv2.COLOR_BGR2BGRA)
207
+ bgra[:, :, 3] = mascara
208
+ if not cv2.imwrite(saida, bgra):
209
+ return {"ok": False, "error": f"não consegui gravar o recorte em {saida}"}
210
+ return {"ok": True, "arquivo": saida, "cobertura": round(cobertura, 3),
211
+ "largura": int(quadro.shape[1]), "altura": int(quadro.shape[0])}
212
+
213
+
178
214
  def main():
179
215
  p = argparse.ArgumentParser(description="Recorte de pessoa do PrimoCode Studio")
180
216
  sub = p.add_subparsers(dest="comando", required=True)
@@ -185,10 +221,26 @@ def main():
185
221
  r.add_argument("--suavizar", type=int, default=7)
186
222
  r.add_argument("--encolher", type=int, default=2)
187
223
  r.add_argument("--limiar", type=float, default=0.6)
224
+ f = sub.add_parser("recortar-foto")
225
+ f.add_argument("--entrada", required=True)
226
+ f.add_argument("--saida", required=True)
227
+ f.add_argument("--suavizar", type=int, default=7)
228
+ f.add_argument("--encolher", type=int, default=2)
229
+ f.add_argument("--limiar", type=float, default=0.6)
188
230
  args = p.parse_args()
189
231
 
190
232
  falta = faltando()
191
233
 
234
+ if args.comando == "recortar-foto":
235
+ # Dentro do motion não se instala nada (o MediaPipe passa de 100 MB): sem ele, a foto vai inteira, com moldura.
236
+ if falta:
237
+ saida = {"ok": False, "semMediaPipe": True, "falta": falta,
238
+ "error": "o recorte de pessoa precisa de " + ", ".join(falta) + " (a foto segue inteira, com moldura)."}
239
+ else:
240
+ saida = recortar_foto(args.entrada, args.saida, args.suavizar, args.encolher, args.limiar)
241
+ print(json.dumps(saida, ensure_ascii=False))
242
+ sys.exit(0 if saida.get("ok") else 1)
243
+
192
244
  if args.comando == "conferir":
193
245
  saida = {"ok": not falta, "falta": falta,
194
246
  "comoResolver": (f"{sys.executable} -m pip install --user " + " ".join(falta)
@@ -86,6 +86,33 @@ def achar_projeto(nome: str) -> "Path | None":
86
86
 
87
87
  PEDIDO_PASTA = re.compile(r"^(?:(?:abre|abrir|abra|troca|trocar|muda|mudar|vai|ir)\s+(?:para|pra|pro|no|na|o|a)?\s*(?:o\s+|a\s+)?(?:projeto|pasta)\s+|trabalha(?:r)?\s+(?:no|na|em)\s+(?:o\s+|a\s+)?(?:projeto\s+|pasta\s+)?)(.+?)[.!?]*$", re.I)
88
88
  VERBO_ACAO = re.compile(r"^(?:e\s+|ent[aã]o\s+|agora\s+)?(?:cria|crie|criar|faz|fa[cç]a|fazer|monta|monte|gera|gere|escreve|escreva|edita|edite|muda|mude|corrige|corrija|conserta|conserte|instala|instale|roda|rode|executa|execute|abre|abra|apaga|apague|deleta|enxuga|enxugue|atualiza|atualize|adiciona|adicione|remove|remova|refatora|publica|publique|coloca|ponha|p[oõ]e|troca|continua|continue|pode ser|sim|isso|ok|beleza|manda|bora|vai)\b", re.I)
89
+ # O 1º motion sem gosto salvo: o motor (em -p, PRIMOCODE_APP=1) escreve no stderr `primocode:abrir-gosto <url>` e espera.
90
+ # O app bloqueia navegador por padrão (PRIMOCODE_SEM_NAVEGADOR=1); ESTA é a exceção explícita: a tela local do gosto abre
91
+ # no navegador padrão. Só a tela local (127.0.0.1 + token hex) passa; qualquer outra coisa na linha é recusada.
92
+ GOSTO_LINHA = re.compile(r"^primocode:abrir-gosto (http://127\.0\.0\.1:\d{2,5}/\?t=[0-9a-f]{32})$")
93
+
94
+
95
+ def url_gosto(linha: str) -> "str | None":
96
+ m = GOSTO_LINHA.match(str(linha or "").strip())
97
+ return m.group(1) if m else None
98
+
99
+
100
+ def abrir_gosto(linha: str, abrir=None) -> bool:
101
+ """True se a linha era do gosto (aberta ou recusada); False se é outra linha qualquer."""
102
+ if not str(linha or "").startswith("primocode:abrir-gosto"):
103
+ return False
104
+ url = url_gosto(linha)
105
+ if not url:
106
+ log("gosto: linha recusada")
107
+ return True
108
+ log("gosto: abrindo a tela do gosto no navegador padrão")
109
+ try:
110
+ (abrir or (lambda u: subprocess.Popen(["open", u], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)))(url)
111
+ except Exception as e:
112
+ log(f"gosto: não abriu: {e}")
113
+ return True
114
+
115
+
89
116
  PEDE_NAVEGADOR = re.compile(r"\babr(?:e|a|ir)\b|\bmostr(?:a|e|ar)\b|navegador|visualiz|preview|pr[eé]-?visualiza", re.I)
90
117
 
91
118
 
@@ -369,7 +396,19 @@ class App:
369
396
  self.cerebro = subprocess.Popen(args, cwd=str(self.pasta), env=env, text=True,
370
397
  stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
371
398
  start_new_session=True) # grupo próprio: nada do que o agente faça com processos atinge o app
372
- out, err = self.cerebro.communicate(timeout=900)
399
+ linhas_err: "list[str]" = []
400
+ cer = self.cerebro
401
+
402
+ def ler_err(): # ao vivo: a linha do gosto tem que abrir a tela ENQUANTO o motor espera
403
+ for l in cer.stderr:
404
+ if not abrir_gosto(l):
405
+ linhas_err.append(l)
406
+ th_err = threading.Thread(target=ler_err, daemon=True)
407
+ th_err.start()
408
+ out = cer.stdout.read()
409
+ cer.wait(timeout=900)
410
+ th_err.join(timeout=2)
411
+ err = "".join(linhas_err)
373
412
  log(f"cérebro: saiu com código {self.cerebro.returncode}")
374
413
  timer.cancel()
375
414
  except Exception as e:
@@ -628,8 +667,8 @@ class Chat:
628
667
  self.js({"conversas": r.get("conversas") or []} if r.get("ok") else {"conversas_erro": r.get("erro") or "Não deu pra carregar as conversas."})
629
668
 
630
669
  def skills(self) -> None:
631
- r = self.node("const S=require('./lib/skills.js');const d=new Set((S.lerEstado().desligadas)||[]);"
632
- "console.log(JSON.stringify({ok:true,skills:S.listar().map(s=>({nome:s.nome,descricao:s.descricao||'',ligada:!d.has(s.nome)}))}))")
670
+ r = self.node("const S=require('./lib/skills.js');" # listar() já diz se cada skill está ligada
671
+ "console.log(JSON.stringify({ok:true,skills:S.listar().map(s=>({nome:s.nome,descricao:s.descricao||'',ligada:s.ligada!==false}))}))")
633
672
  self.js({"skills": r.get("skills") or []} if r.get("ok") else {"erro": r.get("erro")})
634
673
 
635
674
  # ── a ponte ──
@@ -824,7 +863,7 @@ class Chat:
824
863
  for l in p.stderr:
825
864
  if l.startswith("ferramenta:"):
826
865
  self.js({"passo": passo_legivel(l)})
827
- else:
866
+ elif not abrir_gosto(l):
828
867
  resto.append(l)
829
868
  th = threading.Thread(target=passos, daemon=True)
830
869
  th.start()
@@ -121,6 +121,7 @@ a{color:#67e8f9}a:hover{color:#22d3ee}
121
121
  .enviar.vazio .i-voz{opacity:1;transform:none}
122
122
  .enviar.cheio .i-seta{opacity:1;transform:none}
123
123
  .enviar.parar .i-stop{opacity:1;transform:none}
124
+ .enviar.semvoz{visibility:hidden;pointer-events:none}
124
125
  /* mascote e título */
125
126
  .mascote{transition:transform .35s cubic-bezier(.3,1.5,.5,1);filter:drop-shadow(0 0 26px rgba(34,211,238,.35))}
126
127
  .pula-entra{animation:cai .9s cubic-bezier(.3,1.6,.5,1) both}
@@ -464,6 +465,7 @@ function extDe(nome){var m=/\.([A-Za-z0-9]+)$/.exec(nome||'');return m?m[1].toLo
464
465
  var EXT_TIPO={};
465
466
  [['video','mp4 mov m4v webm mkv avi'],['imagem','png jpg jpeg gif webp svg avif heic bmp'],['audio','mp3 wav m4a aac ogg flac'],['pdf','pdf'],['html','html htm'],['codigo','js jsx ts tsx mjs cjs json py sh bash zsh css scss md txt yml yaml toml rb go rs java c h cpp swift kt php sql xml vue lock csv']].forEach(function(p){p[1].split(' ').forEach(function(e){EXT_TIPO[e]=p[0];});});
466
467
  function tipoDe(nome){return EXT_TIPO[extDe(nome)]||'arquivo';}
468
+ function urlArq(c){return estado.arqBase?estado.arqBase+String(c).split('/').map(encodeURIComponent).join('/'):'file://'+encodeURI(c);}
467
469
  function fmtTam(b){
468
470
  b=Number(b);if(!isFinite(b)||b<0)return '';
469
471
  if(b<1024)return b+' B';
@@ -664,7 +666,8 @@ var estado={
664
666
  esperando:false,ignorando:false,passos:[],erro:'',
665
667
  anexos:[],menu:false,aviso:null,
666
668
  skills:null,ajustes:null,busca:'',
667
- comandos:[],pal:{aberta:false,itens:[],sel:0,q:null},abertas:{},confirma:null,provSel:'',chaveDraft:'',chaveSalva:false
669
+ comandos:[],pal:{aberta:false,itens:[],sel:0,q:null},abertas:{},confirma:null,provSel:'',chaveDraft:'',chaveSalva:false,
670
+ semVoz:false,arqBase:'' /* o app desktop manda semVoz (sem motor de voz) e arqBase (o protocolo que serve os arquivos); no Mac ficam vazios */
668
671
  };
669
672
  var animar=false;
670
673
  var mx=0,my=0;
@@ -838,9 +841,10 @@ function htmlSkills(anim){
838
841
  /* ajustes */
839
842
  function tg(a,on,rot,extra){return '<button class="tg'+(on?' on':'')+'" data-a="'+a+'" aria-label="'+esc(rot)+'"'+(extra||'')+'></button>';}
840
843
  function htmlConfig(anim){
841
- var abas=ABAS.map(function(a){
844
+ var abas=ABAS.filter(function(a){return !(estado.semVoz&&a.id==='voz');}).map(function(a){
842
845
  return '<button class="lado-cfg'+(estado.aba===a.id?' on':'')+'" data-a="aba" data-id="'+a.id+'"><span class="tile" style="background:'+a.cor+'">'+ic(a.ico,14,2.2)+'</span>'+a.t+'</button>';
843
846
  }).join('');
847
+ if(estado.semVoz&&estado.aba==='voz')estado.aba='modelos';
844
848
  var corpo={geral:cfgGeral,modelos:cfgModelos,voz:cfgVoz,conta:cfgConta}[estado.aba]();
845
849
  return '<div class="'+(anim?'tela-entra':'')+'" style="width:100%;max-width:860px;align-self:center;margin-bottom:auto;display:flex;flex-wrap:wrap;gap:32px">'+
846
850
  '<nav style="flex:1 1 170px;max-width:210px;display:flex;flex-direction:column;gap:2px"><h2 style="margin:0 0 14px 8px;font-family:\'Instrument Serif\',serif;font-weight:400;font-size:34px">Ajustes</h2>'+abas+'</nav>'+
@@ -1157,13 +1161,14 @@ function renderAvisos(){
1157
1161
  var a=estado.aviso,el=$('avisos');
1158
1162
  if(!a){el.innerHTML='';return;}
1159
1163
  if(a.tipo==='login')el.innerHTML='<div class="cartao-av"><span>Entre na sua conta do PrimoCode pra conversar.</span><span style="display:flex;align-items:center;gap:6px"><button class="entrar" data-a="entrar">Entrar</button><button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button></span></div>';
1164
+ else if(a.tipo==='voz')el.innerHTML='<div class="cartao-av"><span style="min-width:0;overflow-wrap:anywhere">'+esc(a.texto)+'</span><span style="display:flex;align-items:center;gap:6px">'+(a.instalar?'<button class="entrar" data-a="vozInstalar">Instalar a voz</button>':'')+(a.instalando?'':'<button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button>')+'</span></div>';
1160
1165
  else if(a.tipo==='sair')el.innerHTML='<div class="cartao-av"><span>Sair da conta?</span><span style="display:flex;align-items:center;gap:6px"><button class="mc-b" data-a="fechaAviso">Cancelar</button><button class="entrar" style="background:#b8473d" data-a="sairConfirma">Sair</button></span></div>';
1161
1166
  else el.innerHTML='<div class="cartao-av erro"><span style="min-width:0;overflow-wrap:anywhere">'+esc(a.texto)+'</span><button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button></div>';
1162
1167
  }
1163
1168
  function atualizaBotao(){
1164
1169
  var b=$('enviar'),vazio=!ta().value.trim()&&!estado.anexos.length;
1165
1170
  if(estado.esperando){b.className='enviar parar';b.setAttribute('aria-label','Parar');}
1166
- else if(vazio){b.className='enviar vazio';b.setAttribute('aria-label','Abrir a voz do Primo');}
1171
+ else if(vazio){b.className='enviar vazio'+(estado.semVoz?' semvoz':'');b.setAttribute('aria-label','Abrir a voz do Primo');}
1167
1172
  else{b.className='enviar cheio';b.setAttribute('aria-label','Enviar');}
1168
1173
  }
1169
1174
  function cresce(){var t=ta();t.style.height='auto';t.style.height=Math.min(t.scrollHeight,160)+'px';}
@@ -1302,7 +1307,7 @@ function appCmd(c,args){
1302
1307
  var u=ultimaResposta();
1303
1308
  if(u){copiarTexto(u,{textContent:'Copiar'});toast('Copiado');}else toast('Nada pra copiar');
1304
1309
  break;
1305
- case 'voz':post({acao:'voz'});break;
1310
+ case 'voz':if(!estado.semVoz)post({acao:'voz'});break;
1306
1311
  case 'voz_nome':
1307
1312
  estado.aba='voz';post({acao:'ajustes'});
1308
1313
  if(args){post({acao:'ajuste',chave:'voz_nome',valor:String(args)});if(estado.ajustes&&estado.ajustes.voz)estado.ajustes.voz.nome=String(args);}
@@ -1337,7 +1342,7 @@ function enviar(pular){
1337
1342
  if(!estado.logado){estado.aviso={tipo:'login'};renderAvisos();return;}
1338
1343
  var nomes=estado.anexos.map(function(a){return a.nome;});
1339
1344
  var texto=t||('Anexei '+nomes.join(', '));
1340
- var midias=estado.anexos.map(function(a){return {achado:'',caminho:a.caminho,nome:a.nome,tipo:tipoDe(a.nome),url:'file://'+encodeURI(a.caminho)};});
1345
+ var midias=estado.anexos.map(function(a){return {achado:'',caminho:a.caminho,nome:a.nome,tipo:tipoDe(a.nome),url:urlArq(a.caminho)};});
1341
1346
  estado.mensagens.push({papel:'voce',texto:texto,midias:midias});
1342
1347
  estado.tela='chat';estado.aviso=null;estado.erro='';estado.esperando=true;estado.ignorando=false;estado.passos=[];estado.menu=false;
1343
1348
  post({acao:'enviar',texto:texto,conversa:estado.convId==null?null:estado.convId,modo:estado.modo,anexos:estado.anexos.map(function(a){return a.caminho;})});
@@ -1346,7 +1351,7 @@ function enviar(pular){
1346
1351
  }
1347
1352
  function apertar(){
1348
1353
  if(estado.esperando){post({acao:'parar'});estado.esperando=false;estado.passos=[];renderEspera();atualizaBotao();return;}
1349
- if(!ta().value.trim()&&!estado.anexos.length){post({acao:'voz'});return;}
1354
+ if(!ta().value.trim()&&!estado.anexos.length){if(!estado.semVoz)post({acao:'voz'});return;}
1350
1355
  enviar();
1351
1356
  }
1352
1357
  function setModo(v){
@@ -1448,6 +1453,7 @@ document.addEventListener('click',function(e){
1448
1453
  case 'entrar':post({acao:'entrar'});break;
1449
1454
  case 'sair':post({acao:'sair_conta'});break;
1450
1455
  case 'fechaAviso':estado.aviso=null;renderAvisos();break;
1456
+ case 'vozInstalar':estado.aviso={tipo:'voz',texto:'Preparando a voz…',instalando:true};renderAvisos();post({acao:'voz_instalar'});break;
1451
1457
  case 'link':e.preventDefault();post({acao:'abrir_link',url:d.url});break;
1452
1458
  case 'skill':
1453
1459
  var k=estado.skills&&estado.skills[+d.i];
@@ -1598,6 +1604,7 @@ var tratar={
1598
1604
  sincConta(v);
1599
1605
  if(v.pasta!=null)estado.pasta=v.pasta;
1600
1606
  if(v.modo==='chat'||v.modo==='agent')estado.modo=v.modo;
1607
+ estado.semVoz=!!v.semVoz;estado.arqBase=String(v.arqBase||'');atualizaBotao();
1601
1608
  renderConta();renderMenu();renderAvisos();
1602
1609
  if(estado.tela==='config')renderPrincipal();
1603
1610
  if(estado.logado)pedirConversas();
@@ -1655,6 +1662,10 @@ var tratar={
1655
1662
  else{estado.aviso={tipo:'erro',texto:txt};renderAvisos();}
1656
1663
  }else{estado.aviso={tipo:'erro',texto:txt};renderAvisos();}
1657
1664
  },
1665
+ vozAviso:function(v){ /* o app desktop: a voz não está instalada, está instalando ou terminou */
1666
+ if(!v)return;
1667
+ estado.aviso={tipo:'voz',texto:String(v.texto||''),instalar:!!v.instalar,instalando:!!v.instalando};renderAvisos();
1668
+ },
1658
1669
  anexo:function(v){
1659
1670
  if(!v||!v.caminho)return;
1660
1671
  if(estado.anexos.some(function(a){return a.caminho===v.caminho;}))return;
@@ -0,0 +1,134 @@
1
+ """painel_dados.py — os dados do painel (música, calendário, clima, máquina) como um processo à parte.
2
+
3
+ No Mac, quem alimenta o hud.html é o processo da janela (hud.py: as threads `medir()` e `agenda()`). O app desktop (Electron)
4
+ não tem esse processo, então este ajudante roda os MESMOS laços e escreve cada mensagem como uma linha de JSON no stdout:
5
+ o mesmo objeto que o `js(...)` do hud.py manda pra tela (`{"musica":...}`, `{"calendario":...}`, `{"hoje":...}`, `{"clima":...}`,
6
+ `{"maquina":...}`). O app repassa cada linha pra `window.primo(msg)`. O hud.py não muda.
7
+
8
+ sai (stdout): uma mensagem do painel por linha (e, ao iniciar, {"nome":..} e {"robo":..} da identidade)
9
+ entra (stdin): ações da tela, uma por linha: {"acao":"midia","arg":"proxima"}, {"acao":"calendario"},
10
+ {"acao":"volume",...}, {"acao":"app",...}, {"acao":"boot"} (toca o som de abertura)
11
+ fecha o stdin: o ajudante encerra.
12
+ Rode com: python -m primo_voz.painel_dados
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import sys
18
+ import threading
19
+ import time
20
+
21
+ from primo_voz import identidade, tela as td
22
+ from primo_voz import painel as pn
23
+ from primo_voz.sons import Sons
24
+
25
+ # Ações da janela (o app cuida delas): o ajudante ignora.
26
+ DA_JANELA = {"erro", "sair", "chat", "ptt", "entrar", "print"}
27
+
28
+
29
+ def main(entrada=None, saida=None, td_=None, pn_=None, sons=None, passo: float = 1.0) -> int:
30
+ """entrada/saida/td_/pn_/sons/passo: o teste troca por falsos e acelera os laços. `passo` = segundos do laço da máquina (o resto é múltiplo dele)."""
31
+ td_ = td_ or td
32
+ pn_ = pn_ or pn
33
+ entrada = sys.stdin if entrada is None else entrada
34
+ escrever = saida or sys.stdout
35
+ trava = threading.Lock()
36
+ parar = threading.Event()
37
+ sons = sons if sons is not None else Sons(True)
38
+
39
+ def js(msg: dict) -> None:
40
+ with trava:
41
+ try:
42
+ escrever.write(json.dumps(msg, ensure_ascii=False) + "\n")
43
+ escrever.flush()
44
+ except Exception:
45
+ parar.set() # o app fechou o cano: acabou
46
+
47
+ def espera(s: float) -> None:
48
+ parar.wait(s)
49
+
50
+ def medir() -> None:
51
+ am = pn_.Amostrador()
52
+ cache: dict = {}
53
+ k = 0
54
+ while not parar.is_set():
55
+ try:
56
+ js({"maquina": td_.maquina(am)})
57
+ js({"musica": td_.musica()})
58
+ if k % 20 == 0:
59
+ js({"clima": td_.clima_widget(cache)})
60
+ except Exception as e:
61
+ sys.stderr.write(f"painel_dados: {e}\n")
62
+ k += 1
63
+ espera(passo)
64
+
65
+ def agenda() -> None: # em outra thread: o pedido de permissão do Calendário pode demorar
66
+ evs: dict = {"permissao": "pendente", "eventos": []}
67
+ k = 0
68
+ falhas = 0
69
+ while not parar.is_set():
70
+ try:
71
+ if k % 12 == 0 or evs["permissao"] != "ok": # sem acesso, olha a cada 5 s (a pessoa pode ter liberado)
72
+ novo = td_.eventos_do_mes()
73
+ if evs["permissao"] == "ok" and novo["permissao"] != "ok":
74
+ falhas += 1 # erro transitório não derruba o cartão: segura os últimos eventos
75
+ if falhas >= 4:
76
+ evs, falhas = novo, 0
77
+ else:
78
+ evs, falhas = novo, 0
79
+ # SEMPRE manda o estado junto: o painel nunca depende de uma mensagem que pode ter chegado antes da página
80
+ js({"calendario": {"permissao": evs["permissao"], "dias": sorted({e["dia"] for e in evs["eventos"]})}})
81
+ if evs["permissao"] == "ok":
82
+ js({"hoje": td_.hoje_lista(evs["eventos"], time.time())})
83
+ except Exception as e:
84
+ sys.stderr.write(f"painel_dados agenda: {e}\n")
85
+ k += 1
86
+ espera(5 * passo)
87
+
88
+ def acao(m: dict) -> None:
89
+ try:
90
+ _acao(m)
91
+ except Exception as e: # uma ação que estoura não derruba o ajudante
92
+ sys.stderr.write(f"painel_dados: ação {m.get('acao')}: {e}\n")
93
+
94
+ def _acao(m: dict) -> None:
95
+ a = m.get("acao")
96
+ if a in DA_JANELA:
97
+ return
98
+ if a == "boot":
99
+ sons.tocar("boot")
100
+ elif td_.acao(m):
101
+ pass
102
+ else:
103
+ pn_.executar_acao(m)
104
+ if a == "midia":
105
+ espera(0.4 * passo)
106
+ js({"musica": td_.musica()})
107
+
108
+ try:
109
+ ident = identidade.ler()
110
+ js({"nome": ident["nome"]})
111
+ js({"robo": ident["robo"]})
112
+ except Exception as e:
113
+ sys.stderr.write(f"painel_dados: identidade: {e}\n")
114
+ threading.Thread(target=medir, daemon=True).start()
115
+ threading.Thread(target=agenda, daemon=True).start()
116
+ for linha in entrada: # stdin fechado = o app saiu ou voltou pro chat
117
+ if parar.is_set():
118
+ break
119
+ try:
120
+ m = json.loads(linha)
121
+ except ValueError:
122
+ continue
123
+ if not isinstance(m, dict):
124
+ continue
125
+ try:
126
+ threading.Thread(target=acao, args=(m,), daemon=True).start() # ação lenta (osascript) não trava o laço de leitura
127
+ except Exception as e:
128
+ sys.stderr.write(f"painel_dados: ação {m.get('acao')}: {e}\n")
129
+ parar.set()
130
+ return 0
131
+
132
+
133
+ if __name__ == "__main__":
134
+ sys.exit(main())