feat: removido o tamanho do modelo do seletor da tela Editar vídeo

- removido o tamanho do modelo do seletor da tela Editar vídeo
- acelerada a transcrição do Scanner com inferência Whisper em lote e cache de hashes
- garantido um resultado separado para cada clipe de áudio selecionado no Scanner
- adicionado cancelamento do processamento do Scanner pela barra de progresso
- exibido estado visual próprio para Scanner cancelado na barra de progresso
- reorganizada a tela Scanner em blocos separados de imagem e som

Resumo:
- 7 arquivos alterados
- 1 novos
- 6 modificados
- 0 removidos

 6 files changed, 92 insertions(+), 40 deletions(-)

Arquivos:
  - .jhonny/analises.db
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
  - code/engine/integracoes/whisper/provider_de_transcricao_local.py
  - code/engine/scanner/transcricao_da_timeline.py
  - code/engine/testes/test_provider_de_transcricao_local.py
This commit is contained in:
João Henrique
2026-09-09 16:57:29 -04:00
parent 245dcd8015
commit 53a8307f38
7 changed files with 154 additions and 40 deletions
+30 -8
View File
@@ -28,7 +28,7 @@ function log(msg, cls) {
// 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
// reposicionado na aba de origem com etapa atual, barra e tempo decorrido.
var taskState = { active: false, startedAt: 0, timer: null, clearTimer: null, mirror: null, originTab: "silence", originPanelId: "tabSilence", errorText: "" };
var taskState = { active: false, startedAt: 0, timer: null, clearTimer: null, mirror: null, cancel: null, canceled: false, originTab: "silence", originPanelId: "tabSilence", errorText: "" };
var TIPOS_VIDEO_CHAVE = "schedule_light_tipos_video";
var tipoVideoAtualId = null;
@@ -75,11 +75,13 @@ function taskMoveToOriginTab() {
taskState.originPanelId = panel.id;
}
function taskStart(title, stage, mirror) {
function taskStart(title, stage, mirror, cancel) {
if (taskState.clearTimer) { clearTimeout(taskState.clearTimer); taskState.clearTimer = null; }
taskState.active = true;
taskState.startedAt = Date.now();
taskState.mirror = mirror || null;
taskState.cancel = typeof cancel === "function" ? cancel : null;
taskState.canceled = false;
taskState.originTab = activeTab();
taskState.errorText = "";
taskMoveToOriginTab();
@@ -96,6 +98,9 @@ function taskStart(title, stage, mirror) {
taskEl("taskCopy").hidden = true;
taskEl("taskErrorDetails").hidden = true;
taskEl("taskErrorDetails").textContent = "";
taskEl("taskCancel").hidden = !taskState.cancel;
taskEl("taskCancel").disabled = false;
taskEl("taskCancel").textContent = "Cancelar";
hideToast();
if (taskState.timer) clearInterval(taskState.timer);
@@ -127,12 +132,15 @@ function taskEnd(ok, message, technicalDetails) {
taskState.active = false;
var dock = taskEl("taskDock");
dock.setAttribute("data-result", ok ? "ok" : "err");
var cancelado = taskState.canceled;
dock.setAttribute("data-result", cancelado ? "cancelled" : (ok ? "ok" : "err"));
taskEl("taskTrack").setAttribute("data-mode", "determinate");
taskEl("taskFill").style.width = "100%";
taskEl("taskStage").textContent = message || (ok ? "Concluído" : "Falhou");
taskEl("taskStage").textContent = message || (cancelado ? "Cancelado" : (ok ? "Concluído" : "Falhou"));
taskEl("taskPct").textContent = ok ? "100%" : "";
if (!ok) {
taskEl("taskCancel").hidden = true;
taskEl("taskCancel").disabled = false;
if (!ok && !cancelado) {
taskState.errorText = String(technicalDetails || message || "Falha sem detalhes técnicos.");
taskEl("taskCopy").hidden = false;
taskEl("taskErrorDetails").textContent = taskState.errorText;
@@ -141,6 +149,7 @@ function taskEnd(ok, message, technicalDetails) {
}
taskState.mirror = null;
taskState.cancel = null;
// O próprio card já carrega o desfecho e fica na tela por alguns segundos —
// repetir a mesma frase no toast só empilhava dois avisos idênticos.
taskState.clearTimer = setTimeout(function () {
@@ -148,6 +157,15 @@ function taskEnd(ok, message, technicalDetails) {
}, ok ? 4000 : 9000);
}
function taskCancel() {
if (!taskState.active || typeof taskState.cancel !== "function") return;
taskState.canceled = true;
taskEl("taskCancel").disabled = true;
taskEl("taskCancel").textContent = "Cancelando…";
taskEl("taskStage").textContent = "Cancelando…";
try { taskState.cancel(); } catch (erro) { taskEnd(false, "Falha ao cancelar", erro.message); }
}
function taskCopyError() {
var texto = taskState.errorText;
if (!texto) return;
@@ -1090,6 +1108,8 @@ var SCANNER_ALL_RESOURCES = ["faces", "qualidade_facial", "pessoas", "pose", "ma
var scannerTimeline = null;
var scannerReportPath = null;
var scannerProcesso = null;
var scannerCancelSolicitado = false;
var SCANNER_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/executar_scanner.py";
// `path` e `os` são carregados mais abaixo, junto com o runtime Node do CEP.
@@ -1180,10 +1200,12 @@ function scannerProcess() {
var input=path.join(outputRoot,"pedido-"+stamp+".json");
scannerReportPath=path.join(outputRoot,"relatorio-"+stamp+".json");
fs.writeFileSync(input,JSON.stringify({perfil:profile,timeline:scannerTimeline},null,2),"utf-8");
setBusy("btnScannerProcess",true); taskStart("Scanner", "Preparando análise", null);
setBusy("btnScannerProcess",true);
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 child=childProcess.spawn(SCANNER_PYTHON_BIN,[SCANNER_ENGINE_SCRIPT,input,scannerReportPath],spawnOptions);
scannerProcesso=child; scannerCancelSolicitado=false;
taskStart("Scanner", "Preparando análise", null, function () { scannerCancelSolicitado=true; if (scannerProcesso) scannerProcesso.kill("SIGTERM"); });
var stderr="", stdout="";
child.stdout.on("data",function(chunk) {
stdout += chunk.toString(); var lines=stdout.split("\n"); stdout=lines.pop();
@@ -1191,7 +1213,7 @@ function scannerProcess() {
});
child.stderr.on("data",function(chunk){stderr+=chunk.toString();});
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",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.");});
child.on("close",function(code){var foiCancelado=scannerCancelSolicitado;scannerProcesso=null;setBusy("btnScannerProcess",false);if(foiCancelado){taskEnd(false,"Scanner cancelado");return;}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(); }
@@ -3118,7 +3140,7 @@ function syncTranscribeModelSelect() {
modelosDisponiveis.forEach(function (modelo) {
var opt = document.createElement("option");
opt.value = modelo.nome;
opt.textContent = modelo.nome + " · " + formatMB(modelo.tamanho);
opt.textContent = modelo.nome;
select.appendChild(opt);
});
if (!modelosDisponiveis.length) {