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.
- package/LICENSES/edit-ia-pro.txt +32 -0
- package/LICENSES/fontes-OFL.txt +476 -0
- package/LICENSES/hyperframes-Apache-2.0.txt +205 -0
- package/LICENSES/permanent-marker-Apache-2.0.txt +204 -0
- package/LICENSES/selfie-multiclass-Apache-2.0.txt +227 -0
- package/LICENSES/three-MIT.txt +23 -0
- package/LICENSES/video-use-MIT.txt +24 -0
- package/LICENSES/woven-sfx-CC0.txt +16 -0
- package/LICENSES/yunet-MIT.txt +21 -0
- package/README.md +2 -2
- package/bin/primocode.js +28 -8
- package/editor/LICENSE.md +11 -0
- package/editor/README.md +321 -0
- package/editor/SKILL.md +307 -0
- package/editor/THIRD_PARTY_NOTICES.md +228 -0
- package/editor/assets/baixar.json +591 -0
- package/editor/assets/fonts/anton/OFL.txt +93 -0
- package/editor/assets/fonts/caveat/OFL.txt +93 -0
- package/editor/assets/fonts/figtree/OFL.txt +93 -0
- package/editor/assets/fonts/fonts.json +10 -0
- package/editor/assets/fonts/montserrat/OFL.txt +93 -0
- package/editor/assets/fonts/permanentmarker/LICENSE.txt +202 -0
- package/editor/assets/models/SELFIE_MULTICLASS_LICENSE.txt +227 -0
- package/editor/assets/models/YUNET_LICENSE.txt +21 -0
- package/editor/assets/sfx/licencas/bass_drop.txt +32 -0
- package/editor/assets/sfx/licencas/cash.txt +76 -0
- package/editor/assets/sfx/licencas/charge.txt +31 -0
- package/editor/assets/sfx/licencas/click.txt +46 -0
- package/editor/assets/sfx/licencas/coin.txt +50 -0
- package/editor/assets/sfx/licencas/count.txt +47 -0
- package/editor/assets/sfx/licencas/data.txt +31 -0
- package/editor/assets/sfx/licencas/error.txt +32 -0
- package/editor/assets/sfx/licencas/flash.txt +31 -0
- package/editor/assets/sfx/licencas/gears.txt +82 -0
- package/editor/assets/sfx/licencas/keyboard.txt +48 -0
- package/editor/assets/sfx/licencas/metal_hit.txt +62 -0
- package/editor/assets/sfx/licencas/metal_riser.txt +61 -0
- package/editor/assets/sfx/licencas/mouse_click.txt +50 -0
- package/editor/assets/sfx/licencas/notification.txt +54 -0
- package/editor/assets/sfx/licencas/pop.txt +38 -0
- package/editor/assets/sfx/licencas/rewind.txt +32 -0
- package/editor/assets/sfx/licencas/rise.txt +46 -0
- package/editor/assets/sfx/licencas/sinteticos.txt +21 -0
- package/editor/assets/sfx/licencas/snap.txt +50 -0
- package/editor/assets/sfx/licencas/success.txt +40 -0
- package/editor/assets/sfx/licencas/synth_hit.txt +32 -0
- package/editor/assets/sfx/licencas/tick.txt +14 -0
- package/editor/assets/sfx/licencas/whoosh.txt +47 -0
- package/editor/assets/sfx/licencas/wind.txt +31 -0
- package/editor/assets/sfx/sfx.json +832 -0
- package/editor/assets/sfx/sfx_sinteticos.json +317 -0
- package/editor/assets/sfx/sfx_woven.json +387 -0
- package/editor/assets/sfx/woven/LICENCA.md +16 -0
- package/editor/fichas/briefing-do-video.md +45 -0
- package/editor/fichas/conferencia-da-amostra.md +56 -0
- package/editor/fichas/matriz-de-ganchos.csv +3 -0
- package/editor/fichas/padrao.json +16 -0
- package/editor/fichas/planilha-de-cenas.csv +4 -0
- package/editor/fichas/planilha-de-versoes.csv +4 -0
- package/editor/fichas/plano-em-blocos.md +32 -0
- package/editor/fichas/registro-de-materiais.json +27 -0
- package/editor/helpers/assets_ocr/ocr_vision.swift +41 -0
- package/editor/helpers/bordas_precisas.py +272 -0
- package/editor/helpers/camera_viva.py +426 -0
- package/editor/helpers/captions.py +601 -0
- package/editor/helpers/common.py +308 -0
- package/editor/helpers/compose_final.py +3555 -0
- package/editor/helpers/conferir_corte.py +234 -0
- package/editor/helpers/conferir_dados.py +405 -0
- package/editor/helpers/contact_sheet.py +282 -0
- package/editor/helpers/corte_por_onda.py +433 -0
- package/editor/helpers/corte_seco.py +595 -0
- package/editor/helpers/denoise.py +971 -0
- package/editor/helpers/efeitos.py +408 -0
- package/editor/helpers/efeitos_gerar.py +159 -0
- package/editor/helpers/elevenlabs_api.py +442 -0
- package/editor/helpers/face_layout.py +570 -0
- package/editor/helpers/fetch_images.py +423 -0
- package/editor/helpers/ganho_por_trecho.py +214 -0
- package/editor/helpers/gen_image.py +312 -0
- package/editor/helpers/gen_music.py +316 -0
- package/editor/helpers/gen_sfx.py +148 -0
- package/editor/helpers/grade.py +307 -0
- package/editor/helpers/icloud.py +119 -0
- package/editor/helpers/make_sfx.py +940 -0
- package/editor/helpers/matte.py +1342 -0
- package/editor/helpers/medir_referencia.py +2232 -0
- package/editor/helpers/mixar_trilha.py +354 -0
- package/editor/helpers/montar_corte.py +295 -0
- package/editor/helpers/motion/__init__.py +13 -0
- package/editor/helpers/motion/engine.py +1915 -0
- package/editor/helpers/motion/registry.py +510 -0
- package/editor/helpers/motion/render.py +665 -0
- package/editor/helpers/motion/templates/barra_progresso.py +370 -0
- package/editor/helpers/motion/templates/carimbo.py +132 -0
- package/editor/helpers/motion/templates/checklist.py +271 -0
- package/editor/helpers/motion/templates/comentario_cta.py +380 -0
- package/editor/helpers/motion/templates/contador.py +222 -0
- package/editor/helpers/motion/templates/destaque.py +263 -0
- package/editor/helpers/motion/templates/etiqueta_preco.py +461 -0
- package/editor/helpers/motion/templates/grafico_barras.py +510 -0
- package/editor/helpers/motion/templates/grafico_linha.py +462 -0
- package/editor/helpers/motion/templates/notificacao.py +266 -0
- package/editor/helpers/motion/templates/palavra_impacto.py +410 -0
- package/editor/helpers/motion/templates/passos.py +321 -0
- package/editor/helpers/motion/templates/riscado.py +298 -0
- package/editor/helpers/motion/templates/selo_garantia.py +150 -0
- package/editor/helpers/motion_render.py +241 -0
- package/editor/helpers/motion_sugerir.py +961 -0
- package/editor/helpers/noise_check.py +134 -0
- package/editor/helpers/pack_transcripts.py +223 -0
- package/editor/helpers/palavras_na_onda.py +176 -0
- package/editor/helpers/plano_sfx.py +839 -0
- package/editor/helpers/portao.py +427 -0
- package/editor/helpers/preview_server.py +1177 -0
- package/editor/helpers/rastrear_rosto.py +298 -0
- package/editor/helpers/render_cut.py +1256 -0
- package/editor/helpers/respiros.py +89 -0
- package/editor/helpers/sincronizar_cameras.py +571 -0
- package/editor/helpers/speech_regions.py +423 -0
- package/editor/helpers/style_catalog.json +231 -0
- package/editor/helpers/style_samples.py +246 -0
- package/editor/helpers/tela_verde.py +2113 -0
- package/editor/helpers/text_render.py +2059 -0
- package/editor/helpers/texto_na_tela.py +366 -0
- package/editor/helpers/timeline_view.py +292 -0
- package/editor/helpers/tira_de_quadros.py +113 -0
- package/editor/helpers/transcribe.py +734 -0
- package/editor/helpers/transcribe_batch.py +129 -0
- package/editor/helpers/trilhas.py +1146 -0
- package/editor/helpers/trilhas_humores.json +66 -0
- package/editor/helpers/valores.py +187 -0
- package/editor/helpers/verify_cut.py +820 -0
- package/editor/helpers/voice_levels.py +233 -0
- package/editor/helpers/voz_check.py +386 -0
- package/editor/helpers/voz_limpa.py +81 -0
- package/editor/helpers/watch_preview.py +372 -0
- package/editor/modelo-projeto/LEIA.md +402 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Caveat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-JetBrainsMono.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/OFL-Montserrat.txt +93 -0
- package/editor/modelo-projeto/assets/fontes/larguras.json +1 -0
- package/editor/modelo-projeto/assets/three/BufferGeometryUtils.js +1438 -0
- package/editor/modelo-projeto/assets/three/GLTFLoader.js +4895 -0
- package/editor/modelo-projeto/assets/three/LICENSE +21 -0
- package/editor/modelo-projeto/assets/three/ORIGEM.txt +17 -0
- package/editor/modelo-projeto/assets/three/RoomEnvironment.js +185 -0
- package/editor/modelo-projeto/assets/three/three.core.min.js +6 -0
- package/editor/modelo-projeto/assets/three/three.module.min.js +6 -0
- package/editor/modelo-projeto/build.py +4828 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_antes_depois.html +199 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_arquivo.html +232 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_busca.html +315 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_comando.html +304 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_conversa.html +217 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_esquema.html +289 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_medidor.html +249 -0
- package/editor/modelo-projeto/compositions/cenas/2d/k2d_notas.html +233 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_caminho.html +197 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_explodida.html +152 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_tela.html +167 -0
- package/editor/modelo-projeto/compositions/cenas/3d/k3d_volume.html +124 -0
- package/editor/modelo-projeto/compositions/cenas/3d/motor-three.js +728 -0
- package/editor/modelo-projeto/compositions/cenas/LEIA.md +177 -0
- package/editor/modelo-projeto/ferramentas/medir_fontes.py +65 -0
- package/editor/modelo-projeto/ferramentas/pessoa_recortada.py +176 -0
- package/editor/modelo-projeto/ferramentas/rastrear_maos.py +189 -0
- package/editor/modelo-projeto/hyperframes.json +12 -0
- package/editor/modelo-projeto/package.json +11 -0
- package/editor/modelo-projeto/projeto.json +16 -0
- package/editor/preview/app.css +1009 -0
- package/editor/preview/app.js +2122 -0
- package/editor/preview/index.html +307 -0
- package/editor/pyproject.toml +23 -0
- package/editor/references/3d.md +236 -0
- package/editor/references/animacao-catalogo.md +672 -0
- package/editor/references/animacao.md +498 -0
- package/editor/references/comandos.md +92 -0
- package/editor/references/corte-seco-como-funciona.md +50 -0
- package/editor/references/corte-seco.md +88 -0
- package/editor/references/corte.md +290 -0
- package/editor/references/direcao-por-blocos.md +218 -0
- package/editor/references/enquadramento.md +654 -0
- package/editor/references/estilos-prontos.md +355 -0
- package/editor/references/estudio.md +100 -0
- package/editor/references/exemplo-eventos.json +67 -0
- package/editor/references/lettering.md +252 -0
- package/editor/references/modelos.md +494 -0
- package/editor/references/phase2.md +1007 -0
- package/editor/references/referencia-aplicar.md +203 -0
- package/editor/references/referencia-medir.md +375 -0
- package/editor/references/revisao.md +335 -0
- package/editor/references/som-e-trilha.md +146 -0
- package/editor/references/tabela-de-sons.md +130 -0
- package/editor/references/variacoes.md +234 -0
- package/editor/uv.lock +1141 -0
- package/lib/act.js +36 -8
- package/lib/catalogo.js +4 -0
- package/lib/chamada-torta.js +81 -0
- package/lib/editor-pro.js +786 -0
- package/lib/entrega.js +58 -0
- package/lib/estudio/conferencia-visual.js +76 -0
- package/lib/estudio/diretor.js +6 -1
- package/lib/estudio/exportar.js +3 -1
- package/lib/estudio/gosto.js +45 -4
- package/lib/estudio/index.js +8 -5
- package/lib/estudio/kit-ae/base.jsx +119 -13
- package/lib/estudio/kit-ae/cenas.jsx +47 -15
- package/lib/estudio/kit-ae/guarda.jsx +274 -0
- package/lib/estudio/kit-ae/primitivas.jsx +180 -24
- package/lib/estudio/maquina-diretor.js +61 -7
- package/lib/estudio/maquina-foto.js +177 -0
- package/lib/estudio/maquina-guarda.js +207 -0
- package/lib/estudio/maquina-layout.js +22 -1
- package/lib/estudio/maquina.js +60 -11
- package/lib/estudio/referencias.js +1 -1
- package/lib/estudio/visao/visao.swift +97 -0
- package/lib/estudio/visao.js +60 -0
- package/lib/local/cerebro.json +192 -6
- package/lib/local/motor.js +5 -2
- package/lib/mcp.js +105 -7
- package/lib/nuvem.js +11 -1
- package/lib/outra-pasta.js +1 -1
- package/lib/passagem.js +50 -0
- package/lib/perigo.js +29 -12
- package/lib/protecao.js +134 -0
- package/lib/sandbox.js +11 -4
- package/lib/titulo-conversa.js +46 -0
- package/lib/tools.js +3 -0
- package/lib/ui.js +1 -1
- package/package.json +3 -2
- package/skills/editar-meu-video/SKILL.md +6 -2
- package/skills/editar-video/SKILL.md +113 -0
- package/studio/pessoa.py +52 -0
- package/voz/primo_voz/app.py +43 -4
- package/voz/primo_voz/chat.html +17 -6
- 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)
|
package/voz/primo_voz/app.py
CHANGED
|
@@ -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
|
-
|
|
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');
|
|
632
|
-
"console.log(JSON.stringify({ok:true,skills:S.listar().map(s=>({nome:s.nome,descricao:s.descricao||'',ligada
|
|
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
|
-
|
|
866
|
+
elif not abrir_gosto(l):
|
|
828
867
|
resto.append(l)
|
|
829
868
|
th = threading.Thread(target=passos, daemon=True)
|
|
830
869
|
th.start()
|
package/voz/primo_voz/chat.html
CHANGED
|
@@ -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:
|
|
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())
|