Compare commits
3
Commits
ee181360c8
...
5cc50f00a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cc50f00a4 | ||
|
|
0a01b16148 | ||
|
|
b2a8b1f24c |
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: boas-praticas-oo
|
||||||
|
description: Padrões obrigatórios de POO, nomenclatura em PT-BR e documentação para todo código Python de code/engine/ neste projeto (Jhonny). Use SEMPRE — antes de criar, editar, corrigir, refatorar ou revisar qualquer classe, função ou módulo Python do engine, mesmo sem pedido explícito do usuário. Não se aplica a outros projetos.
|
||||||
|
---
|
||||||
|
|
||||||
# Skill — Boas práticas gerais para desenvolvimento Python orientado a objetos
|
# Skill — Boas práticas gerais para desenvolvimento Python orientado a objetos
|
||||||
|
|
||||||
## 1. Objetivo
|
## 1. Objetivo
|
||||||
|
|||||||
Executable
+211
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Hook PostToolUse: verifica conformidade básica com
|
||||||
|
`.agents/skills/boas-praticas-oo/SKILL.md` em arquivos Python de
|
||||||
|
`code/engine/` recém-escritos ou editados por Claude Code.
|
||||||
|
|
||||||
|
Este script não persiste dados e não é importado pelo `engine` — é uma
|
||||||
|
ferramenta de infraestrutura de desenvolvimento, acionada pelo hook
|
||||||
|
configurado em `.claude/settings.json`.
|
||||||
|
|
||||||
|
Ele recebe no stdin o JSON de entrada do hook (evento PostToolUse, tool
|
||||||
|
Write ou Edit), lê o arquivo do disco (já gravado nesse ponto) e checa,
|
||||||
|
via `ast`, se o arquivo tem: docstring de módulo, docstring em toda
|
||||||
|
classe pública, docstring em toda função/método público, e nenhum
|
||||||
|
`except` genérico demais (`except:` puro, ou `except Exception:` cujo
|
||||||
|
corpo é só `pass`).
|
||||||
|
|
||||||
|
Ele não verifica nomenclatura em PT-BR (a checagem por regex geraria
|
||||||
|
falsos positivos demais para identificadores técnicos/externos) — isso
|
||||||
|
continua sendo responsabilidade da revisão humana ou do `/code-review`.
|
||||||
|
|
||||||
|
Quando encontra violações, devolve um JSON com `"decision": "block"` e
|
||||||
|
`"reason"` explicando o que falta, para que o próprio Claude Code corrija
|
||||||
|
antes de considerar a tarefa concluída. Nunca bloqueia a gravação em si
|
||||||
|
(o arquivo já foi escrito quando o hook roda) — o bloqueio é apenas o
|
||||||
|
sinal que leva o agente a corrigir na mesma tarefa.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def arquivo_e_alvo_da_verificacao(caminho: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Decide se um caminho deve ser verificado por esta checagem.
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
caminho: Caminho absoluto do arquivo editado ou criado.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
True quando o arquivo é um `.py` dentro de `code/engine/`.
|
||||||
|
"""
|
||||||
|
partes = caminho.parts
|
||||||
|
return caminho.suffix == ".py" and "code" in partes and "engine" in partes
|
||||||
|
|
||||||
|
|
||||||
|
def arquivo_e_de_teste(caminho: Path) -> bool:
|
||||||
|
"""
|
||||||
|
Decide se um arquivo é um módulo de teste, onde funções/métodos de
|
||||||
|
teste ficam isentos da exigência de docstring (o nome do teste já
|
||||||
|
documenta o comportamento, convenção adotada pela skill `tdd`).
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
caminho: Caminho absoluto do arquivo.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
True quando o arquivo está em `code/engine/testes/` ou seu nome
|
||||||
|
começa com ``test_``.
|
||||||
|
"""
|
||||||
|
return "testes" in caminho.parts or caminho.name.startswith("test_")
|
||||||
|
|
||||||
|
|
||||||
|
def coletar_violacoes_de_docstring(arvore: ast.Module, e_arquivo_de_teste: bool) -> list[str]:
|
||||||
|
"""
|
||||||
|
Coleta violações de docstring obrigatória em módulo, classes e funções públicas.
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
arvore: AST do arquivo já analisado.
|
||||||
|
e_arquivo_de_teste: Quando True, não exige docstring em funções
|
||||||
|
e métodos (apenas em módulo e classes) — testes se
|
||||||
|
documentam pelo nome.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
Lista de mensagens de violação, uma por item sem docstring.
|
||||||
|
"""
|
||||||
|
violacoes: list[str] = []
|
||||||
|
|
||||||
|
if not ast.get_docstring(arvore):
|
||||||
|
violacoes.append("Falta docstring de módulo no topo do arquivo.")
|
||||||
|
|
||||||
|
for no in arvore.body:
|
||||||
|
if isinstance(no, ast.ClassDef) and not no.name.startswith("_"):
|
||||||
|
if not ast.get_docstring(no):
|
||||||
|
violacoes.append(f"Linha {no.lineno}: classe '{no.name}' sem docstring.")
|
||||||
|
if not e_arquivo_de_teste:
|
||||||
|
violacoes.extend(_violacoes_de_metodos(no))
|
||||||
|
elif isinstance(no, (ast.FunctionDef, ast.AsyncFunctionDef)) and not e_arquivo_de_teste:
|
||||||
|
if not no.name.startswith("_") and not ast.get_docstring(no):
|
||||||
|
violacoes.append(f"Linha {no.lineno}: função '{no.name}' sem docstring.")
|
||||||
|
|
||||||
|
return violacoes
|
||||||
|
|
||||||
|
|
||||||
|
def _violacoes_de_metodos(classe: ast.ClassDef) -> list[str]:
|
||||||
|
"""
|
||||||
|
Coleta violações de docstring nos métodos públicos de uma classe.
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
classe: Nó da classe cujos métodos serão verificados.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
Lista de mensagens de violação para métodos públicos sem docstring.
|
||||||
|
"""
|
||||||
|
violacoes = []
|
||||||
|
for membro in classe.body:
|
||||||
|
if isinstance(membro, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
|
if not membro.name.startswith("_") and not ast.get_docstring(membro):
|
||||||
|
violacoes.append(
|
||||||
|
f"Linha {membro.lineno}: método '{classe.name}.{membro.name}' sem docstring.")
|
||||||
|
return violacoes
|
||||||
|
|
||||||
|
|
||||||
|
def coletar_violacoes_de_except_generico(arvore: ast.Module) -> list[str]:
|
||||||
|
"""
|
||||||
|
Coleta usos de `except` genéricos demais: bare `except:` e
|
||||||
|
`except Exception:` cujo corpo é só `pass`.
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
arvore: AST do arquivo já analisado.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
Lista de mensagens de violação, uma por `except` problemático.
|
||||||
|
"""
|
||||||
|
violacoes: list[str] = []
|
||||||
|
for no in ast.walk(arvore):
|
||||||
|
if not isinstance(no, ast.ExceptHandler):
|
||||||
|
continue
|
||||||
|
e_bare = no.type is None
|
||||||
|
e_exception_generica = isinstance(no.type, ast.Name) and no.type.id == "Exception"
|
||||||
|
corpo_e_so_pass = len(no.body) == 1 and isinstance(no.body[0], ast.Pass)
|
||||||
|
if e_bare:
|
||||||
|
violacoes.append(
|
||||||
|
f"Linha {no.lineno}: 'except:' genérico captura tudo, inclusive "
|
||||||
|
"KeyboardInterrupt/SystemExit — use uma exceção específica.")
|
||||||
|
elif e_exception_generica and corpo_e_so_pass:
|
||||||
|
violacoes.append(
|
||||||
|
f"Linha {no.lineno}: 'except Exception: pass' engole o erro "
|
||||||
|
"silenciosamente — trate, registre ou levante uma exceção específica.")
|
||||||
|
return violacoes
|
||||||
|
|
||||||
|
|
||||||
|
def verificar(caminho: Path) -> list[str]:
|
||||||
|
"""
|
||||||
|
Executa todas as checagens de boas práticas sobre um arquivo Python.
|
||||||
|
|
||||||
|
Parâmetros:
|
||||||
|
caminho: Caminho absoluto do arquivo a verificar.
|
||||||
|
|
||||||
|
Retorna:
|
||||||
|
Lista de mensagens de violação encontradas. Lista vazia quando o
|
||||||
|
arquivo está conforme, o arquivo não existe mais, ou o conteúdo
|
||||||
|
tem erro de sintaxe (não é responsabilidade deste hook apontar
|
||||||
|
erros de sintaxe).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
codigo_fonte = caminho.read_text(encoding="utf-8")
|
||||||
|
except OSError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
arvore = ast.parse(codigo_fonte, filename=str(caminho))
|
||||||
|
except SyntaxError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
e_arquivo_de_teste = arquivo_e_de_teste(caminho)
|
||||||
|
return (
|
||||||
|
coletar_violacoes_de_docstring(arvore, e_arquivo_de_teste)
|
||||||
|
+ coletar_violacoes_de_except_generico(arvore)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""
|
||||||
|
Ponto de entrada do hook: lê o evento no stdin e reporta violações encontradas.
|
||||||
|
|
||||||
|
Não gera efeitos colaterais além de imprimir no stdout o JSON de
|
||||||
|
saída esperado pelo contrato de hooks do Claude Code. Sempre encerra
|
||||||
|
com código de saída 0 — o "bloqueio" é comunicado via
|
||||||
|
``{"decision": "block"}``, não via código de saída, porque o arquivo
|
||||||
|
já foi gravado quando este hook roda (evento PostToolUse).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
entrada = json.load(sys.stdin)
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
return
|
||||||
|
|
||||||
|
caminho_bruto = (entrada.get("tool_input") or {}).get("file_path")
|
||||||
|
if not caminho_bruto:
|
||||||
|
return
|
||||||
|
|
||||||
|
caminho = Path(caminho_bruto).resolve()
|
||||||
|
if not arquivo_e_alvo_da_verificacao(caminho):
|
||||||
|
return
|
||||||
|
|
||||||
|
violacoes = verificar(caminho)
|
||||||
|
if not violacoes:
|
||||||
|
return
|
||||||
|
|
||||||
|
motivo = (
|
||||||
|
f"Padrões de código (.agents/skills/boas-praticas-oo/SKILL.md) "
|
||||||
|
f"não atendidos em {caminho.name}:\n- " + "\n- ".join(violacoes)
|
||||||
|
)
|
||||||
|
print(json.dumps({"decision": "block", "reason": motivo}, ensure_ascii=False))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -8,6 +8,11 @@
|
|||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
|
"command": "\"$CLAUDE_PROJECT_DIR/rag/reindex_hook.sh\"",
|
||||||
"async": true
|
"async": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/verificar_boas_praticas.py\"",
|
||||||
|
"statusMessage": "Verificando boas práticas OO (PT-BR, docstrings, exceções)..."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,3 +28,6 @@ build/
|
|||||||
# Temporary files
|
# Temporary files
|
||||||
*.tmp
|
*.tmp
|
||||||
*.temp
|
*.temp
|
||||||
|
|
||||||
|
# Resumo temporário usado pelo admin/deploy.command para detalhar o commit
|
||||||
|
admin/COMMIT-DETAILS.md
|
||||||
|
|||||||
@@ -2,9 +2,22 @@
|
|||||||
|
|
||||||
## Project Skills
|
## Project Skills
|
||||||
|
|
||||||
|
## Resumo obrigatório para commits
|
||||||
|
|
||||||
|
Toda pessoa, agente ou IA que implementar qualquer alteração neste projeto
|
||||||
|
deve, antes de finalizar o trabalho, acrescentar **uma única linha** ao final
|
||||||
|
de `admin/COMMIT-DETAILS.md`, em PT-BR, resumindo o que foi feito. Nunca
|
||||||
|
substituir nem apagar as linhas já registradas durante o lote. A linha deve
|
||||||
|
ser curta, sem credenciais, tokens ou dados sensíveis; por exemplo:
|
||||||
|
`- corrigido o carregamento do Scanner quando executado pelo painel CEP`.
|
||||||
|
|
||||||
|
O script `admin/deploy.command` incorpora todas as linhas na mensagem do
|
||||||
|
commit e limpa o arquivo somente após o commit ser concluído com sucesso. Se
|
||||||
|
o commit falhar, os resumos devem ser preservados para nova tentativa.
|
||||||
|
|
||||||
## Convenções obrigatórias de Python / OO
|
## Convenções obrigatórias de Python / OO
|
||||||
|
|
||||||
Todo código Python em `code/engine/` deve seguir a skill **`code/engine/skills/boas-praticas-oo.md`** (boas práticas de programação orientada a objetos). Leia-a antes de criar, modificar, corrigir, revisar, refatorar ou ampliar qualquer sistema Python.
|
Todo código Python em `code/engine/` deve seguir a skill **`.agents/skills/boas-praticas-oo/SKILL.md`** (boas práticas de programação orientada a objetos) — obrigatória, aplicada automaticamente, não apenas quando solicitada. Leia-a antes de criar, modificar, corrigir, revisar, refatorar ou ampliar qualquer sistema Python.
|
||||||
|
|
||||||
Resumo das regras vinculantes:
|
Resumo das regras vinculantes:
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
## Project Context
|
## Project Context
|
||||||
|
|
||||||
|
## Registro obrigatório de alterações
|
||||||
|
|
||||||
|
Ao concluir qualquer implementação, acrescentar uma única linha em
|
||||||
|
`admin/COMMIT-DETAILS.md` descrevendo resumidamente o que foi alterado. A
|
||||||
|
linha deve ser adicionada ao final do arquivo, sem apagar os registros
|
||||||
|
anteriores do lote. O resumo não pode conter segredos ou tokens. O
|
||||||
|
`admin/deploy.command` usa essas linhas na mensagem do commit e limpa o
|
||||||
|
arquivo somente depois de um commit bem-sucedido.
|
||||||
|
|
||||||
This is a software engineering project that uses mattpocock/skills for code quality and organization.
|
This is a software engineering project that uses mattpocock/skills for code quality and organization.
|
||||||
|
|
||||||
## Code Search: use the RAG first
|
## Code Search: use the RAG first
|
||||||
@@ -22,11 +31,24 @@ need to see the matched code, `--map` for a file inventory, and `--path`/
|
|||||||
tunnel available (`rag/ensure_tunnel.sh` failed) or for exact-string
|
tunnel available (`rag/ensure_tunnel.sh` failed) or for exact-string
|
||||||
searches it isn't built for (e.g. searching non-code config not indexed).
|
searches it isn't built for (e.g. searching non-code config not indexed).
|
||||||
|
|
||||||
|
## Mandatory skill — read before writing any Python in code/engine/
|
||||||
|
|
||||||
|
**`.agents/skills/boas-praticas-oo/SKILL.md` is not optional.** It is not
|
||||||
|
triggered by a request — apply it automatically on every Python file created,
|
||||||
|
edited, fixed, refactored or reviewed under `code/engine/`: PT-BR identifiers
|
||||||
|
for everything internal, docstrings on every module/class/public method,
|
||||||
|
single-responsibility classes, explicit dependency injection, specific
|
||||||
|
exceptions (never bare `except Exception`), input validation, and tests for
|
||||||
|
the behavior touched. See [CODING_STANDARDS.md](CODING_STANDARDS.md) for the
|
||||||
|
normative summary, and run its checklist (skill section 41) before calling
|
||||||
|
any Python change in `code/engine/` done.
|
||||||
|
|
||||||
## Available Skills
|
## Available Skills
|
||||||
|
|
||||||
The following skills are available in this project (located in `.agents/skills/`):
|
The following skills are available in this project (located in `.agents/skills/`):
|
||||||
|
|
||||||
### Code Quality & Architecture
|
### Code Quality & Architecture
|
||||||
|
- **`boas-praticas-oo`** - **Mandatory**, always-on. OO standards + PT-BR naming for all `code/engine/` Python (see above)
|
||||||
- **`/improve-codebase-architecture`** - Scan codebase for deepening opportunities
|
- **`/improve-codebase-architecture`** - Scan codebase for deepening opportunities
|
||||||
- **`/codebase-design`** - Design deep modules with simple interfaces
|
- **`/codebase-design`** - Design deep modules with simple interfaces
|
||||||
- **`/domain-modeling`** - Build shared vocabulary and domain model
|
- **`/domain-modeling`** - Build shared vocabulary and domain model
|
||||||
@@ -49,7 +71,7 @@ When working on this project, use these skills to:
|
|||||||
|
|
||||||
## Code Standards
|
## Code Standards
|
||||||
|
|
||||||
- Follow existing code conventions
|
- Python in `code/engine/`: follow `.agents/skills/boas-praticas-oo/SKILL.md` and [CODING_STANDARDS.md](CODING_STANDARDS.md) — mandatory, not a suggestion
|
||||||
- Use shared domain vocabulary from CONTEXT.md
|
- Use shared domain vocabulary from CONTEXT.md
|
||||||
- Write tests before code (TDD)
|
- Write tests before code (TDD)
|
||||||
- Review code changes with `/code-review`
|
- Review code changes with `/code-review`
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Este documento consolida as convenções obrigatórias de código do projeto. Serve de fonte de verdade para revisões automáticas (eixo *Standards* do `/code-review`) e vale para qualquer agente que escreva ou altere código.
|
Este documento consolida as convenções obrigatórias de código do projeto. Serve de fonte de verdade para revisões automáticas (eixo *Standards* do `/code-review`) e vale para qualquer agente que escreva ou altere código.
|
||||||
|
|
||||||
A fonte completa das regras de orientação a objeto é a skill **`code/engine/skills/boas-praticas-oo.md`**. O que está abaixo é o resumo normativo; em caso de dúvida, leia a skill por inteiro.
|
A fonte completa das regras de orientação a objeto é a skill **`.agents/skills/boas-praticas-oo/SKILL.md`**. O que está abaixo é o resumo normativo; em caso de dúvida, leia a skill por inteiro.
|
||||||
|
|
||||||
## Escopo
|
## Escopo
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -1,9 +1,11 @@
|
|||||||
# DEV-NOTES — Registro de Alterações (Jhonny Editor)
|
# DEV-NOTES — Registro de Alterações (Jhonny Editor)
|
||||||
|
|
||||||
> **Como usar:**
|
> **Como usar:**
|
||||||
> 1. Ao fazer alterações, adicione bullets na seção `## NOVO NESTE LOTE`
|
> 1. Para alterações feitas pelo agente, use `admin/COMMIT-DETAILS.md`.
|
||||||
> 2. Rod `admin/deploy.command` — ele lê essas notas e usa como descrição do commit
|
> 2. Rode `admin/deploy.command` — ele usa o resumo como detalhe do commit.
|
||||||
> 3. Após o deploy, mova os bullets para `## Histórico` e esvazie `NOVO NESTE LOTE`
|
> 3. Após um commit bem-sucedido, o arquivo temporário é limpo automaticamente.
|
||||||
|
>
|
||||||
|
> O bloco abaixo continua como fallback para notas manuais ou histórico do projeto.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -21,6 +21,7 @@ umask 077
|
|||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
ADMIN="$ROOT/admin"
|
ADMIN="$ROOT/admin"
|
||||||
NOTES="$ADMIN/DEV-NOTES.md"
|
NOTES="$ADMIN/DEV-NOTES.md"
|
||||||
|
COMMIT_DETAILS="$ADMIN/COMMIT-DETAILS.md"
|
||||||
REMOTE="origin"
|
REMOTE="origin"
|
||||||
BRANCH="main"
|
BRANCH="main"
|
||||||
|
|
||||||
@@ -57,10 +58,16 @@ echo " ... ($(echo "$CHANGES" | grep -c .) itens)"
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# 2) Extrair notas do DEV-NOTES.md
|
# 2) Extrair detalhes preparados pelo agente
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
MSG_NOTA=""
|
MSG_NOTA=""
|
||||||
if [[ -f "$NOTES" && -s "$NOTES" ]]; then
|
if [[ -f "$COMMIT_DETAILS" && -s "$COMMIT_DETAILS" ]]; then
|
||||||
|
MSG_NOTA="$(sed 's/^[[:space:]]*//; s/[[:space:]]*$//' "$COMMIT_DETAILS" \
|
||||||
|
| grep -v '^$' \
|
||||||
|
| grep -v '^<!--' \
|
||||||
|
| grep -v '^-->' || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z "$MSG_NOTA" && -f "$NOTES" && -s "$NOTES" ]]; then
|
||||||
MSG_NOTA="$(awk '
|
MSG_NOTA="$(awk '
|
||||||
/^## NOVO NESTE LOTE/ {f=1; next}
|
/^## NOVO NESTE LOTE/ {f=1; next}
|
||||||
/^## / && f {exit}
|
/^## / && f {exit}
|
||||||
@@ -124,6 +131,9 @@ fi
|
|||||||
say "Adicionando arquivos e commitando..."
|
say "Adicionando arquivos e commitando..."
|
||||||
git add -A
|
git add -A
|
||||||
git commit -m "$COMMIT_MSG" || die "commit falhou"
|
git commit -m "$COMMIT_MSG" || die "commit falhou"
|
||||||
|
if [[ -f "$COMMIT_DETAILS" ]]; then
|
||||||
|
: > "$COMMIT_DETAILS"
|
||||||
|
fi
|
||||||
ok "commit criado: $(git rev-parse --short HEAD)"
|
ok "commit criado: $(git rev-parse --short HEAD)"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|||||||
@@ -42,13 +42,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Painel de progresso global: tudo que está rodando aparece aqui, no topo,
|
<!-- O painel é reposicionado para a aba que iniciou a operação. -->
|
||||||
em vez de ficar escondido no log lá embaixo. -->
|
|
||||||
<section class="task-dock" id="taskDock" role="status" aria-live="polite" hidden>
|
<section class="task-dock" id="taskDock" role="status" aria-live="polite" hidden>
|
||||||
<div class="task-head">
|
<div class="task-head">
|
||||||
<span class="task-spinner" id="taskSpinner" aria-hidden="true"></span>
|
<span class="task-spinner" id="taskSpinner" aria-hidden="true"></span>
|
||||||
<strong class="task-title" id="taskTitle">Processando…</strong>
|
<strong class="task-title" id="taskTitle">Processando…</strong>
|
||||||
<span class="task-elapsed" id="taskElapsed">00:00</span>
|
<span class="task-elapsed" id="taskElapsed">00:00</span>
|
||||||
|
<button type="button" class="task-copy" id="taskCopy" onclick="taskCopyError()" aria-label="Copiar erro técnico" title="Copiar erro técnico" hidden>⧉</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-track" id="taskTrack" data-mode="indeterminate">
|
<div class="progress-track" id="taskTrack" data-mode="indeterminate">
|
||||||
<div class="progress-fill" id="taskFill" style="width:0%"></div>
|
<div class="progress-fill" id="taskFill" style="width:0%"></div>
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
<span class="task-stage" id="taskStage">Preparando…</span>
|
<span class="task-stage" id="taskStage">Preparando…</span>
|
||||||
<span class="task-pct" id="taskPct"></span>
|
<span class="task-pct" id="taskPct"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<pre class="task-error-details" id="taskErrorDetails" hidden></pre>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="toast" id="toast" role="status" aria-live="polite" hidden>
|
<div class="toast" id="toast" role="status" aria-live="polite" hidden>
|
||||||
|
|||||||
+84
-22
@@ -27,8 +27,8 @@ function log(msg, cls) {
|
|||||||
// ---- Painel de progresso (task dock) ----
|
// ---- Painel de progresso (task dock) ----
|
||||||
// Antes, tudo que rodava só aparecia como linhas no log lá embaixo e a pessoa
|
// Antes, tudo que rodava só aparecia como linhas no log lá embaixo e a pessoa
|
||||||
// não sabia o que estava acontecendo. Agora todo processo longo abre um card
|
// não sabia o que estava acontecendo. Agora todo processo longo abre um card
|
||||||
// fixo no topo com etapa atual, barra e tempo decorrido.
|
// reposicionado na aba de origem com etapa atual, barra e tempo decorrido.
|
||||||
var taskState = { active: false, startedAt: 0, timer: null, clearTimer: null, mirror: null };
|
var taskState = { active: false, startedAt: 0, timer: null, clearTimer: null, mirror: null, originTab: "silence", originPanelId: "tabSilence", errorText: "" };
|
||||||
|
|
||||||
function taskEl(id) { return document.getElementById(id); }
|
function taskEl(id) { return document.getElementById(id); }
|
||||||
|
|
||||||
@@ -39,11 +39,28 @@ function formatClock(ms) {
|
|||||||
return mm + ":" + ss;
|
return mm + ":" + ss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activeTab() {
|
||||||
|
var panel = document.querySelector(".tab-panel:not([hidden])");
|
||||||
|
return panel ? panel.id.replace(/^tab/, "").toLowerCase() : "silence";
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskMoveToOriginTab() {
|
||||||
|
var dock = taskEl("taskDock");
|
||||||
|
var nome = taskState.originTab;
|
||||||
|
var panel = document.getElementById("tab" + nome.charAt(0).toUpperCase() + nome.slice(1));
|
||||||
|
if (!dock || !panel) return;
|
||||||
|
panel.insertBefore(dock, panel.firstChild);
|
||||||
|
taskState.originPanelId = panel.id;
|
||||||
|
}
|
||||||
|
|
||||||
function taskStart(title, stage, mirror) {
|
function taskStart(title, stage, mirror) {
|
||||||
if (taskState.clearTimer) { clearTimeout(taskState.clearTimer); taskState.clearTimer = null; }
|
if (taskState.clearTimer) { clearTimeout(taskState.clearTimer); taskState.clearTimer = null; }
|
||||||
taskState.active = true;
|
taskState.active = true;
|
||||||
taskState.startedAt = Date.now();
|
taskState.startedAt = Date.now();
|
||||||
taskState.mirror = mirror || null;
|
taskState.mirror = mirror || null;
|
||||||
|
taskState.originTab = activeTab();
|
||||||
|
taskState.errorText = "";
|
||||||
|
taskMoveToOriginTab();
|
||||||
|
|
||||||
var dock = taskEl("taskDock");
|
var dock = taskEl("taskDock");
|
||||||
dock.removeAttribute("data-result");
|
dock.removeAttribute("data-result");
|
||||||
@@ -54,6 +71,9 @@ function taskStart(title, stage, mirror) {
|
|||||||
taskEl("taskElapsed").textContent = "00:00";
|
taskEl("taskElapsed").textContent = "00:00";
|
||||||
taskEl("taskTrack").setAttribute("data-mode", "indeterminate");
|
taskEl("taskTrack").setAttribute("data-mode", "indeterminate");
|
||||||
taskEl("taskFill").style.width = "0%";
|
taskEl("taskFill").style.width = "0%";
|
||||||
|
taskEl("taskCopy").hidden = true;
|
||||||
|
taskEl("taskErrorDetails").hidden = true;
|
||||||
|
taskEl("taskErrorDetails").textContent = "";
|
||||||
hideToast();
|
hideToast();
|
||||||
|
|
||||||
if (taskState.timer) clearInterval(taskState.timer);
|
if (taskState.timer) clearInterval(taskState.timer);
|
||||||
@@ -80,7 +100,7 @@ function taskUpdate(info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function taskEnd(ok, message) {
|
function taskEnd(ok, message, technicalDetails) {
|
||||||
if (taskState.timer) { clearInterval(taskState.timer); taskState.timer = null; }
|
if (taskState.timer) { clearInterval(taskState.timer); taskState.timer = null; }
|
||||||
taskState.active = false;
|
taskState.active = false;
|
||||||
|
|
||||||
@@ -90,6 +110,13 @@ function taskEnd(ok, message) {
|
|||||||
taskEl("taskFill").style.width = "100%";
|
taskEl("taskFill").style.width = "100%";
|
||||||
taskEl("taskStage").textContent = message || (ok ? "Concluído" : "Falhou");
|
taskEl("taskStage").textContent = message || (ok ? "Concluído" : "Falhou");
|
||||||
taskEl("taskPct").textContent = ok ? "100%" : "";
|
taskEl("taskPct").textContent = ok ? "100%" : "";
|
||||||
|
if (!ok) {
|
||||||
|
taskState.errorText = String(technicalDetails || message || "Falha sem detalhes técnicos.");
|
||||||
|
taskEl("taskCopy").hidden = false;
|
||||||
|
taskEl("taskErrorDetails").textContent = taskState.errorText;
|
||||||
|
taskEl("taskErrorDetails").hidden = false;
|
||||||
|
if (activeTab() !== taskState.originTab) switchTab(taskState.originTab);
|
||||||
|
}
|
||||||
|
|
||||||
taskState.mirror = null;
|
taskState.mirror = null;
|
||||||
// O próprio card já carrega o desfecho e fica na tela por alguns segundos —
|
// O próprio card já carrega o desfecho e fica na tela por alguns segundos —
|
||||||
@@ -99,6 +126,35 @@ function taskEnd(ok, message) {
|
|||||||
}, ok ? 4000 : 9000);
|
}, ok ? 4000 : 9000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function taskCopyError() {
|
||||||
|
var texto = taskState.errorText;
|
||||||
|
if (!texto) return;
|
||||||
|
try {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(texto).then(function () { showToast("ok", "Erro técnico copiado."); }).catch(function () { taskCopyErrorFallback(texto); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
taskCopyErrorFallback(texto);
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskCopyErrorFallback(texto) {
|
||||||
|
try {
|
||||||
|
var copia = childProcess.spawn("pbcopy");
|
||||||
|
copia.stdin.end(texto);
|
||||||
|
showToast("ok", "Erro técnico copiado.");
|
||||||
|
return;
|
||||||
|
} catch (_) {}
|
||||||
|
var area = document.createElement("textarea");
|
||||||
|
area.value = texto;
|
||||||
|
area.style.position = "fixed";
|
||||||
|
area.style.opacity = "0";
|
||||||
|
document.body.appendChild(area);
|
||||||
|
area.select();
|
||||||
|
try { document.execCommand("copy"); showToast("ok", "Erro técnico copiado."); } catch (erro) { showToast("err", "Não foi possível copiar o erro: " + erro.message); }
|
||||||
|
document.body.removeChild(area);
|
||||||
|
}
|
||||||
|
|
||||||
function showToast(kind, text) {
|
function showToast(kind, text) {
|
||||||
var el = document.getElementById("toast");
|
var el = document.getElementById("toast");
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -174,6 +230,10 @@ function switchTab(name) {
|
|||||||
// A aba apenas controla o processo e mostra resultados; a análise inteira
|
// A aba apenas controla o processo e mostra resultados; a análise inteira
|
||||||
// acontece na engine (scanner/retakes), chamada via executar_retakes.py.
|
// acontece na engine (scanner/retakes), chamada via executar_retakes.py.
|
||||||
var RETAKES_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/executar_retakes.py";
|
var RETAKES_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/executar_retakes.py";
|
||||||
|
// A engine usa tipos do Python 3.10+ (ex.: `X | None`, `dict[str, ...]`), que
|
||||||
|
// o `/usr/bin/python3` do macOS (3.9) não aceita. Usar o mesmo venv do projeto
|
||||||
|
// (Python 3.14, com faster_whisper) garante que Scanner e Retakes carreguem bem.
|
||||||
|
var SCANNER_PYTHON_BIN = "/Volumes/Merongo/SISTEMAS/venvs/whisperx-transcricao/bin/python";
|
||||||
var retakesCasoAtual = null;
|
var retakesCasoAtual = null;
|
||||||
var retakesGrupos = [];
|
var retakesGrupos = [];
|
||||||
var retakesProcesso = null;
|
var retakesProcesso = null;
|
||||||
@@ -298,21 +358,22 @@ function retakesAnalisar() {
|
|||||||
taskStart("Retakes", "Preparando análise", null);
|
taskStart("Retakes", "Preparando análise", null);
|
||||||
document.getElementById("retakesProgressoCard").hidden = false;
|
document.getElementById("retakesProgressoCard").hidden = false;
|
||||||
document.getElementById("retakesProgressoEtapa").textContent = "Analisando retakes…";
|
document.getElementById("retakesProgressoEtapa").textContent = "Analisando retakes…";
|
||||||
var child = childProcess.spawn("python3", [RETAKES_ENGINE_SCRIPT, entrada, path.join(retakesRaiz(), retakesCasoAtual)],
|
var child = childProcess.spawn(SCANNER_PYTHON_BIN, [RETAKES_ENGINE_SCRIPT, entrada, path.join(retakesRaiz(), retakesCasoAtual)],
|
||||||
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine", stdio: ["ignore", "pipe", "pipe"] });
|
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine", stdio: ["ignore", "pipe", "pipe"] });
|
||||||
retakesProcesso = child;
|
retakesProcesso = child;
|
||||||
var resto = "";
|
var resto = "";
|
||||||
|
var erroTecnico = "";
|
||||||
child.stdout.on("data", function (pedaco) {
|
child.stdout.on("data", function (pedaco) {
|
||||||
resto += pedaco.toString(); var linhas = resto.split("\n"); resto = linhas.pop();
|
resto += pedaco.toString(); var linhas = resto.split("\n"); resto = linhas.pop();
|
||||||
linhas.forEach(function (linha) { var evento; try { evento = JSON.parse(linha); } catch (_) { return; } retakesProcessarEvento(evento); });
|
linhas.forEach(function (linha) { var evento; try { evento = JSON.parse(linha); } catch (_) { return; } retakesProcessarEvento(evento); });
|
||||||
});
|
});
|
||||||
child.stderr.on("data", function (pedaco) { if (pedaco.toString().trim()) retakesLog(pedaco.toString().trim()); });
|
child.stderr.on("data", function (pedaco) { erroTecnico += pedaco.toString(); if (pedaco.toString().trim()) retakesLog(pedaco.toString().trim()); });
|
||||||
child.on("error", function (erro) { retakesProcesso = null; setBusy("btnRetakesAnalisar", false); taskEnd(false, "Engine indisponível"); showToast("err", erro.message); });
|
child.on("error", function (erro) { retakesProcesso = null; setBusy("btnRetakesAnalisar", false); taskEnd(false, "Engine indisponível", erro.stack || erroTecnico || erro.message); showToast("err", erro.message); });
|
||||||
child.on("close", function (codigo) {
|
child.on("close", function (codigo) {
|
||||||
retakesProcesso = null; setBusy("btnRetakesAnalisar", false);
|
retakesProcesso = null; setBusy("btnRetakesAnalisar", false);
|
||||||
document.getElementById("retakesProgressoCard").hidden = true;
|
document.getElementById("retakesProgressoCard").hidden = true;
|
||||||
document.getElementById("retakesStatusGeral").textContent = "Analisado";
|
document.getElementById("retakesStatusGeral").textContent = "Analisado";
|
||||||
if (codigo !== 0) taskEnd(false, "Análise falhou");
|
if (codigo !== 0) taskEnd(false, "Análise falhou", erroTecnico || "A engine terminou com código " + codigo + ".");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +397,7 @@ function retakesRevisar(grupoId, acao) {
|
|||||||
var pedido = { acao: "revisar", video_id: retakesCasoAtual, revisao: { grupo_id: grupoId, acao: acao } };
|
var pedido = { acao: "revisar", video_id: retakesCasoAtual, revisao: { grupo_id: grupoId, acao: acao } };
|
||||||
var entrada = path.join(os.tmpdir(), "revisao-retakes-" + Date.now() + ".json");
|
var entrada = path.join(os.tmpdir(), "revisao-retakes-" + Date.now() + ".json");
|
||||||
fs.writeFileSync(entrada, JSON.stringify(pedido), "utf-8");
|
fs.writeFileSync(entrada, JSON.stringify(pedido), "utf-8");
|
||||||
var child = childProcess.spawn("python3", [RETAKES_ENGINE_SCRIPT, entrada, path.join(retakesRaiz(), retakesCasoAtual)],
|
var child = childProcess.spawn(SCANNER_PYTHON_BIN, [RETAKES_ENGINE_SCRIPT, entrada, path.join(retakesRaiz(), retakesCasoAtual)],
|
||||||
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine", stdio: ["ignore", "pipe", "pipe"] });
|
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine", stdio: ["ignore", "pipe", "pipe"] });
|
||||||
child.on("close", function (codigo) {
|
child.on("close", function (codigo) {
|
||||||
if (codigo !== 0) { showToast("err", "Não foi possível registrar a revisão."); return; }
|
if (codigo !== 0) { showToast("err", "Não foi possível registrar a revisão."); return; }
|
||||||
@@ -454,15 +515,15 @@ function scannerProcess() {
|
|||||||
setBusy("btnScannerProcess",true); taskStart("Scanner", "Preparando análise", null);
|
setBusy("btnScannerProcess",true); taskStart("Scanner", "Preparando análise", null);
|
||||||
var spawnOptions={cwd:"/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code",stdio:["ignore","pipe","pipe"]};
|
var spawnOptions={cwd:"/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code",stdio:["ignore","pipe","pipe"]};
|
||||||
var huggingFaceEnvironment=hfSpawnOptions(); if(huggingFaceEnvironment&&huggingFaceEnvironment.env) spawnOptions.env=huggingFaceEnvironment.env;
|
var huggingFaceEnvironment=hfSpawnOptions(); if(huggingFaceEnvironment&&huggingFaceEnvironment.env) spawnOptions.env=huggingFaceEnvironment.env;
|
||||||
var child=childProcess.spawn("python3",[SCANNER_ENGINE_SCRIPT,input,scannerReportPath],spawnOptions);
|
var child=childProcess.spawn(SCANNER_PYTHON_BIN,[SCANNER_ENGINE_SCRIPT,input,scannerReportPath],spawnOptions);
|
||||||
var stderr="", stdout="";
|
var stderr="", stdout="";
|
||||||
child.stdout.on("data",function(chunk) {
|
child.stdout.on("data",function(chunk) {
|
||||||
stdout += chunk.toString(); var lines=stdout.split("\n"); stdout=lines.pop();
|
stdout += chunk.toString(); var lines=stdout.split("\n"); stdout=lines.pop();
|
||||||
lines.forEach(function(line) { try { var event=JSON.parse(line); if (event.evento === "progresso") taskUpdate({stage:event.etapa, detail:event.clipe || "", pct:event.percentual}); if (event.evento === "concluido" && event.arquivo) scannerReportPath=event.arquivo; } catch (_) {} });
|
lines.forEach(function(line) { try { var event=JSON.parse(line); if (event.evento === "progresso") taskUpdate({stage:event.etapa, detail:event.clipe || "", pct:event.percentual}); if (event.evento === "concluido" && event.arquivo) scannerReportPath=event.arquivo; } catch (_) {} });
|
||||||
});
|
});
|
||||||
child.stderr.on("data",function(chunk){stderr+=chunk.toString();});
|
child.stderr.on("data",function(chunk){stderr+=chunk.toString();});
|
||||||
child.on("error",function(error){setBusy("btnScannerProcess",false);taskEnd(false,"Scanner indisponível");showToast("err",error.message);});
|
child.on("error",function(error){setBusy("btnScannerProcess",false);taskEnd(false,"Scanner indisponível",error.stack||error.message);showToast("err",error.message);});
|
||||||
child.on("close",function(code){setBusy("btnScannerProcess",false);if(code!==0||!fs.existsSync(scannerReportPath)){taskEnd(false,"Scanner falhou");showToast("err",stderr||"Não foi possível gerar o relatório.");return;}taskEnd(true,"Relatório JSON gerado");document.getElementById("scannerReportPath").textContent=scannerReportPath;document.getElementById("scannerReportCard").hidden=false;showToast("ok","Scanner concluído.");});
|
child.on("close",function(code){setBusy("btnScannerProcess",false);if(code!==0||!fs.existsSync(scannerReportPath)){taskEnd(false,"Scanner falhou",stderr||"Não foi possível gerar o relatório.");showToast("err",stderr||"Não foi possível gerar o relatório.");return;}taskEnd(true,"Relatório JSON gerado");document.getElementById("scannerReportPath").textContent=scannerReportPath;document.getElementById("scannerReportCard").hidden=false;showToast("ok","Scanner concluído.");});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scannerOpenReport() { if (!scannerReportPath) return; childProcess.spawn("open",[scannerReportPath],{detached:true,stdio:"ignore"}).unref(); }
|
function scannerOpenReport() { if (!scannerReportPath) return; childProcess.spawn("open",[scannerReportPath],{detached:true,stdio:"ignore"}).unref(); }
|
||||||
@@ -1450,6 +1511,7 @@ function runStreamed(bin, args, onLine, onDone, options) {
|
|||||||
var childProcess = nodeRequire("child_process");
|
var childProcess = nodeRequire("child_process");
|
||||||
var child = childProcess.spawn(bin, args, options || {});
|
var child = childProcess.spawn(bin, args, options || {});
|
||||||
var buffer = "";
|
var buffer = "";
|
||||||
|
var ultimoErro = "";
|
||||||
// Linhas "@@PROGRESS {json}" são o canal de progresso dos scripts Python/Node:
|
// Linhas "@@PROGRESS {json}" são o canal de progresso dos scripts Python/Node:
|
||||||
// vão para a barra no topo em vez de virarem ruído no log.
|
// vão para a barra no topo em vez de virarem ruído no log.
|
||||||
function handleLine(line) {
|
function handleLine(line) {
|
||||||
@@ -1471,11 +1533,11 @@ function runStreamed(bin, args, onLine, onDone, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
child.stdout.on("data", function (d) { flushLines(d.toString()); });
|
child.stdout.on("data", function (d) { flushLines(d.toString()); });
|
||||||
child.stderr.on("data", function (d) { flushLines(d.toString()); });
|
child.stderr.on("data", function (d) { ultimoErro = d.toString().trim() || ultimoErro; flushLines(d.toString()); });
|
||||||
child.on("error", function (err) { onDone(1, "spawn error: " + err.message); });
|
child.on("error", function (err) { onDone(1, "spawn error: " + err.message); });
|
||||||
child.on("close", function (code) {
|
child.on("close", function (code) {
|
||||||
if (buffer.trim()) handleLine(buffer);
|
if (buffer.trim()) handleLine(buffer);
|
||||||
onDone(code, null);
|
onDone(code, ultimoErro || null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,11 +1565,11 @@ function silenceTranscribe() {
|
|||||||
"--cache-dir", silenceState.outputDir,
|
"--cache-dir", silenceState.outputDir,
|
||||||
],
|
],
|
||||||
function (line) { lastLine = line; silenceLog(line); },
|
function (line) { lastLine = line; silenceLog(line); },
|
||||||
function (code) {
|
function (code, lastErr) {
|
||||||
setBusy("btnTranscribe", false);
|
setBusy("btnTranscribe", false);
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
setStep(2, "error", "Falhou");
|
setStep(2, "error", "Falhou");
|
||||||
taskEnd(false, "A transcrição falhou (código " + code + "). Veja os detalhes técnicos.");
|
taskEnd(false, "A transcrição falhou (código " + code + "). Veja os detalhes técnicos.", lastErr || lastLine);
|
||||||
silenceLog("Falha na transcrição (código " + code + ").", "err");
|
silenceLog("Falha na transcrição (código " + code + ").", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1515,7 +1577,7 @@ function silenceTranscribe() {
|
|||||||
try { parsed = JSON.parse(lastLine); } catch (e) {}
|
try { parsed = JSON.parse(lastLine); } catch (e) {}
|
||||||
if (!parsed || parsed.ok === false) {
|
if (!parsed || parsed.ok === false) {
|
||||||
setStep(2, "error", "Falhou");
|
setStep(2, "error", "Falhou");
|
||||||
taskEnd(false, "A transcrição terminou com uma resposta inválida.");
|
taskEnd(false, "A transcrição terminou com uma resposta inválida.", lastLine);
|
||||||
silenceLog("Transcrição terminou mas resposta é inválida: " + lastLine, "err");
|
silenceLog("Transcrição terminou mas resposta é inválida: " + lastLine, "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1573,7 +1635,7 @@ function silenceDiarizeSpeakers() {
|
|||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
setStep(3, "ready", "Opcional");
|
setStep(3, "ready", "Opcional");
|
||||||
info.textContent = "Falhou — veja os detalhes técnicos.";
|
info.textContent = "Falhou — veja os detalhes técnicos.";
|
||||||
taskEnd(false, "A detecção de locutores falhou (código " + code + ").");
|
taskEnd(false, "A detecção de locutores falhou (código " + code + ").", lastErr);
|
||||||
silenceLog("Falha na diarização (código " + code + (lastErr ? ": " + lastErr : "") + ").", "err");
|
silenceLog("Falha na diarização (código " + code + (lastErr ? ": " + lastErr : "") + ").", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1628,7 +1690,7 @@ function silenceComputeVoiceFeatures() {
|
|||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
setStep(3, "ready", "Opcional");
|
setStep(3, "ready", "Opcional");
|
||||||
info.textContent = "Falhou — veja os detalhes técnicos.";
|
info.textContent = "Falhou — veja os detalhes técnicos.";
|
||||||
taskEnd(false, "O cálculo das métricas de voz falhou (código " + code + ").");
|
taskEnd(false, "O cálculo das métricas de voz falhou (código " + code + ").", lastErr);
|
||||||
silenceLog("Falha ao calcular métricas de voz (código " + code + (lastErr ? ": " + lastErr : "") + ").", "err");
|
silenceLog("Falha ao calcular métricas de voz (código " + code + (lastErr ? ": " + lastErr : "") + ").", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1784,11 +1846,11 @@ function applyEditorialActions() {
|
|||||||
nodeBin,
|
nodeBin,
|
||||||
[applyScript, "--plan", planPath, "--sequence", silenceState.sequenceName],
|
[applyScript, "--plan", planPath, "--sequence", silenceState.sequenceName],
|
||||||
function (line) { silenceLog(line); },
|
function (line) { silenceLog(line); },
|
||||||
function (code) {
|
function (code, lastErr) {
|
||||||
setBusy("btnApplyEditorialActions", false);
|
setBusy("btnApplyEditorialActions", false);
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
setStep(5, "error", "Falhou");
|
setStep(5, "error", "Falhou");
|
||||||
taskEnd(false, "Falha ao aplicar o plano (código " + code + ").");
|
taskEnd(false, "Falha ao aplicar o plano (código " + code + ").", lastErr);
|
||||||
silenceLog("Falha ao aplicar o plano (código " + code + ").", "err");
|
silenceLog("Falha ao aplicar o plano (código " + code + ").", "err");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2374,12 +2436,12 @@ function downloadModel(model, btn, approxMB) {
|
|||||||
"--approx-mb", String(approxMB || 0),
|
"--approx-mb", String(approxMB || 0),
|
||||||
],
|
],
|
||||||
function (line) { modelsLog(line); },
|
function (line) { modelsLog(line); },
|
||||||
function (code) {
|
function (code, lastErr) {
|
||||||
btn.classList.remove("is-busy");
|
btn.classList.remove("is-busy");
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
if (box) box.hidden = true;
|
if (box) box.hidden = true;
|
||||||
modelsLog("Falha ao baixar " + model + " (código " + code + ").", "err");
|
modelsLog("Falha ao baixar " + model + " (código " + code + ").", "err");
|
||||||
taskEnd(false, "Falha ao baixar o modelo " + model + " (código " + code + ").");
|
taskEnd(false, "Falha ao baixar o modelo " + model + " (código " + code + ").", lastErr);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ h1, h2, h3 { margin: 0; }
|
|||||||
.task-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
.task-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||||
.task-title { flex: 1; min-width: 0; overflow: hidden; font-size: 11.5px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
|
.task-title { flex: 1; min-width: 0; overflow: hidden; font-size: 11.5px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.task-elapsed { flex: 0 0 auto; color: var(--text-muted); font: 10px var(--font-mono); }
|
.task-elapsed { flex: 0 0 auto; color: var(--text-muted); font: 10px var(--font-mono); }
|
||||||
|
.task-copy { flex: 0 0 auto; padding: 2px 5px; border: 1px solid var(--border); border-radius: 4px; background: transparent; color: var(--text-secondary); cursor: pointer; font-size: 12px; line-height: 1; }
|
||||||
|
.task-copy:hover { border-color: var(--red); color: var(--text); }
|
||||||
|
|
||||||
.task-spinner {
|
.task-spinner {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
@@ -209,6 +211,7 @@ h1, h2, h3 { margin: 0; }
|
|||||||
.task-foot { display: flex; align-items: baseline; gap: 8px; margin-top: 7px; }
|
.task-foot { display: flex; align-items: baseline; gap: 8px; margin-top: 7px; }
|
||||||
.task-stage { flex: 1; min-width: 0; overflow: hidden; color: var(--text-secondary); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
.task-stage { flex: 1; min-width: 0; overflow: hidden; color: var(--text-secondary); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.task-pct { flex: 0 0 auto; color: var(--text); font: 600 10px var(--font-mono); }
|
.task-pct { flex: 0 0 auto; color: var(--text); font: 600 10px var(--font-mono); }
|
||||||
|
.task-error-details { max-height: 180px; margin: 10px 0 0; padding: 8px; overflow: auto; border: 1px solid rgba(255, 101, 101, .25); border-radius: 5px; background: rgba(0, 0, 0, .2); color: #ffb0b0; font: 9px/1.45 var(--font-mono); white-space: pre-wrap; user-select: text; }
|
||||||
|
|
||||||
/* -------------------------------- Toast -------------------------------- */
|
/* -------------------------------- Toast -------------------------------- */
|
||||||
.toast {
|
.toast {
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ import sys
|
|||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# O painel chama este arquivo com cwd ``code/engine`` e o Python inclui apenas
|
||||||
|
# essa pasta no caminho de importação. Injetar ``code`` (pai do pacote
|
||||||
|
# ``engine``) para que os importes da engine funcionem, como em
|
||||||
|
# ``executar_scanner.py``.
|
||||||
|
CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent
|
||||||
|
if str(CAMINHO_DO_CODIGO) not in sys.path:
|
||||||
|
sys.path.insert(0, str(CAMINHO_DO_CODIGO))
|
||||||
|
|
||||||
from engine.scanner.retakes import (
|
from engine.scanner.retakes import (
|
||||||
STATUS_CONFIRMADO,
|
STATUS_CONFIRMADO,
|
||||||
STATUS_IGNORADO,
|
STATUS_IGNORADO,
|
||||||
|
|||||||
@@ -5,9 +5,16 @@ import json
|
|||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
# O painel chama este arquivo diretamente, portanto o Python inclui apenas
|
||||||
|
# ``code/engine`` no caminho de importação. O pacote público fica em ``code``.
|
||||||
|
CAMINHO_DO_CODIGO = Path(__file__).resolve().parent.parent
|
||||||
|
if str(CAMINHO_DO_CODIGO) not in sys.path:
|
||||||
|
sys.path.insert(0, str(CAMINHO_DO_CODIGO))
|
||||||
|
|
||||||
from engine.integracoes.premiere.conversores import ConversorDeTimeline
|
from engine.integracoes.premiere.conversores import ConversorDeTimeline
|
||||||
from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual
|
from engine.scanner import ConfiguracaoVisualDoScanner, criar_detector_apple_vision, gerar_relatorio_visual
|
||||||
from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline
|
from engine.scanner.transcricao_da_timeline import TranscricaoDaTimeline
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""Testes do ponto de entrada usado pelo painel CEP para o Scanner."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TesteDoPontoDeEntradaDoScanner(unittest.TestCase):
|
||||||
|
"""Garante que o Scanner possa ser iniciado pelo caminho usado no painel."""
|
||||||
|
|
||||||
|
def test_ponto_de_entrada_direto_carrega_o_pacote_engine(self):
|
||||||
|
"""Executa o arquivo diretamente sem falhar ao importar ``engine``."""
|
||||||
|
caminho_do_script = Path(__file__).resolve().parents[1] / "executar_scanner.py"
|
||||||
|
resultado = subprocess.run(
|
||||||
|
[sys.executable, str(caminho_do_script), "--help"],
|
||||||
|
cwd=caminho_do_script.parent.parent,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resultado.returncode, 0, resultado.stderr)
|
||||||
|
self.assertIn("usage:", resultado.stdout)
|
||||||
|
self.assertNotIn("No module named 'engine'", resultado.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user