feat: adicionado atalho para abrir a pasta e copiar o caminho do J

- adicionado atalho para abrir a pasta e copiar o caminho do J-SOM gerado
- criada a tela Refinar para identificar falantes e ouvir seus trechos de áudio

Resumo:
- 3 arquivos alterados
- 0 novos
- 3 modificados
- 0 removidos

 3 files changed, 145 insertions(+), 15 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
This commit is contained in:
João Henrique
2026-09-09 20:43:35 -04:00
parent 19d10e61a1
commit 06e4d13e86
3 changed files with 145 additions and 15 deletions
+111 -15
View File
@@ -245,6 +245,7 @@ function switchTab(name) {
var tabs = [
{ key: "silence", panel: "tabSilence", btn: "tabBtnSilence" },
{ key: "scanner", panel: "tabScanner", btn: "tabBtnScanner" },
{ key: "refinar", panel: "tabRefinar", btn: "tabBtnRefinar" },
{ key: "retakes", panel: "tabRetakes", btn: "tabBtnRetakes" },
{ key: "analises", panel: "tabAnalises", btn: "tabBtnAnalises" },
{ key: "tipos-video", panel: "tabTiposVideo", btn: "tabBtnTiposVideo" },
@@ -267,6 +268,7 @@ function switchTab(name) {
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); }
if (name === "settings") renderEditorPersonalities();
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); scannerListarAnalisesExistentes(); }
if (name === "refinar") refinarAoAbrir();
if (name === "retakes") retakesAoAbrir();
if (name === "analises") analisesAoAbrir();
if (name === "tipos-video") tiposVideoAoAbrir();
@@ -771,7 +773,8 @@ function tipoVideoCopiarJson() {
} catch (erro) { showToast("err", erro.message); }
}
function copiarTextoParaAreaDeTransferencia(texto) {
function copiarTextoParaAreaDeTransferencia(texto, mensagem) {
mensagem = mensagem || "Conteúdo copiado para a área de transferência.";
function copiarComSelecaoDoPainel() {
var area = document.createElement("textarea");
area.value = texto;
@@ -785,7 +788,7 @@ function copiarTextoParaAreaDeTransferencia(texto) {
var copiado = false;
try { copiado = document.execCommand("copy"); } catch (_) { copiado = false; }
document.body.removeChild(area);
if (copiado) showToast("ok", "JSON resumido copiado para a área de transferência.");
if (copiado) showToast("ok", mensagem);
else showToast("err", "Não foi possível copiar o JSON. Selecione e copie manualmente.");
}
@@ -795,23 +798,23 @@ function copiarTextoParaAreaDeTransferencia(texto) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(texto).then(function () {
showToast("ok", "JSON resumido copiado para a área de transferência.");
showToast("ok", mensagem);
}).catch(copiarComSelecaoDoPainel);
return;
}
} catch (_) {}
copiarComSelecaoDoPainel();
});
}, mensagem);
}
function copiarComPbcopy(texto, fallback) {
function copiarComPbcopy(texto, fallback, mensagem) {
try {
var copia = childProcess.spawn("pbcopy");
var finalizado = false;
function concluir(copiado) {
if (finalizado) return;
finalizado = true;
if (copiado) showToast("ok", "JSON resumido copiado para a área de transferência.");
if (copiado) showToast("ok", mensagem || "Conteúdo copiado para a área de transferência.");
else fallback();
}
copia.once("error", function () { concluir(false); });
@@ -912,6 +915,26 @@ function analisesCopiar() {
} catch (error) { showToast("err", error.message); }
}
function abrirPastaDoJsonGerado() {
if (!silenceState.jsonOutputPath) return;
try {
childProcess.spawn("open", [path.dirname(silenceState.jsonOutputPath)], { detached: true, stdio: "ignore" }).unref();
} catch (erro) {
showToast("err", "Não foi possível abrir a pasta no Finder: " + erro.message);
}
}
function copiarCaminhoDoJsonGerado() {
if (!silenceState.jsonOutputPath) return;
copiarComPbcopy(
silenceState.jsonOutputPath,
function () {
copiarTextoParaAreaDeTransferencia(silenceState.jsonOutputPath, "Caminho do JSON copiado para a área de transferência.");
},
"Caminho do JSON copiado para a área de transferência."
);
}
// ---- Retakes ---------------------------------------------------------------
// A aba apenas controla o processo e mostra resultados; a análise inteira
// acontece na engine (scanner/retakes), chamada via executar_retakes.py.
@@ -2169,9 +2192,11 @@ function silenceLoadScannerAnalysis() {
var status = document.getElementById("scannerAnalysisStatus");
var info = document.getElementById("scannerAnalysisInfo");
setBusy("btnLoadScannerAnalysis", true);
setBusy("btnRefinarLoad", true);
var caminhoDoRelatorio = silenceLocalizarUltimoRelatorioDoScanner();
if (!caminhoDoRelatorio) {
setBusy("btnLoadScannerAnalysis", false);
setBusy("btnRefinarLoad", false);
status.textContent = "Não encontrada";
info.textContent = "Execute o Scanner primeiro para criar uma análise reutilizável.";
showToast("err", info.textContent);
@@ -2199,7 +2224,8 @@ function silenceLoadScannerAnalysis() {
var turnos = transcricao.segments.filter(function (segmento) { return segmento.falante; }).map(function (segmento) {
return { speaker: segmento.falante, start: segmento.start, end: segmento.end };
});
if (falantes.length) renderSpeakerConfiguration(falantes, turnos);
renderSpeakerConfiguration(falantes, turnos);
atualizarTelaRefinar(falantes, audioDoBanco);
status.textContent = "Carregada";
info.textContent = silenceState.sequenceName + " · " + quantidade + " trecho(s) · " +
(audioDoBanco.metricas_de_voz_disponiveis ? "métricas disponíveis" : "métricas não realizadas") + " · " +
@@ -2224,6 +2250,36 @@ function silenceLoadScannerAnalysis() {
showToast("err", info.textContent);
}
setBusy("btnLoadScannerAnalysis", false);
setBusy("btnRefinarLoad", false);
}
function refinarAoAbrir() {
var info = document.getElementById("refinarInfo");
if (!info || !silenceState.sequenceId) return;
info.textContent = silenceState.sequenceName + " · " + (silenceState.speakers.length
? silenceState.speakers.length + " falante(s) carregado(s)."
: "Nenhum falante carregado.");
}
function refinarCarregarAnalise() {
silenceLoadScannerAnalysis();
}
function atualizarTelaRefinar(falantes, audioDoBanco) {
var status = document.getElementById("refinarStatus");
var info = document.getElementById("refinarInfo");
var vazio = document.getElementById("refinarSpeakerEmpty");
var count = document.getElementById("refinarSpeakerCount");
var save = document.getElementById("btnRefinarSave");
if (!status || !info || !vazio || !count) return;
var quantidade = Array.isArray(falantes) ? falantes.length : 0;
status.textContent = quantidade ? "Pronto" : "Sem diarização";
info.textContent = silenceState.sequenceName + " · " + (audioDoBanco && audioDoBanco.diarizacao_disponivel
? quantidade + " falante(s) detectado(s)."
: "A transcrição está disponível, mas a diarização não foi realizada.");
count.textContent = quantidade ? quantidade + (quantidade === 1 ? " falante detectado" : " falantes detectados") : "Nenhum falante carregado";
vazio.hidden = quantidade > 0;
if (save) save.disabled = quantidade === 0;
}
var speakerAudioPlayer = null;
@@ -2490,6 +2546,7 @@ function silenceSelectSequence() {
setStep(2, "ready", "Sua vez");
document.getElementById("btnTranscribe").disabled = false;
document.getElementById("silenceJsonCard").hidden = true;
document.getElementById("jsonFileActions").hidden = true;
document.getElementById("silencePlanInfo").textContent = "";
silenceLog("Timeline escolhida: " + result.sequenceName + " — " + result.mediaPath);
renderCachedTranscripts();
@@ -2748,6 +2805,7 @@ function silenceDiarizeSpeakers(onFinished) {
if (parsed && Array.isArray(parsed.speakers)) {
silenceState.speakerTurns = Array.isArray(parsed.turns) ? parsed.turns : [];
renderSpeakerConfiguration(parsed.speakers, silenceState.speakerTurns);
atualizarTelaRefinar(parsed.speakers, { diarizacao_disponivel: true });
}
var msg = parsed
? "Foram detectados " + parsed.num_speakers + (parsed.num_speakers === 1 ? " falante" : " falantes") + ": " + parsed.speakers.join(", ") + "."
@@ -2767,20 +2825,45 @@ function speakerNameDefault(speakerId) {
return speakerId;
}
function renderSpeakerConfiguration(speakerIds, speakerTurns) {
var config = document.getElementById("silenceSpeakerConfig");
var list = document.getElementById("silenceSpeakerList");
var count = document.getElementById("silenceSpeakerCount");
function carregarNomesDeFalantes() {
if (!silenceState.sequenceId) return {};
try {
var salvo = JSON.parse(localStorage.getItem("mcp_falantes_" + silenceState.sequenceId) || "{}");
return salvo && typeof salvo === "object" ? salvo : {};
} catch (_) { return {}; }
}
function salvarNomesDeFalantes() {
if (!silenceState.sequenceId) return;
var salvo = {};
silenceState.speakers.forEach(function (speaker) {
salvo[speaker.id] = { name: speaker.name, active: speaker.active !== false };
});
try { localStorage.setItem("mcp_falantes_" + silenceState.sequenceId, JSON.stringify(salvo)); } catch (_) {}
}
function refinarSalvarFalantes() {
salvarNomesDeFalantes();
showToast("ok", "Identificação dos falantes salva.");
}
function renderSpeakerConfiguration(speakerIds, speakerTurns, alvo) {
alvo = alvo || {};
var config = document.getElementById(alvo.config || "silenceSpeakerConfig");
var list = document.getElementById(alvo.list || "silenceSpeakerList");
var count = document.getElementById(alvo.count || "silenceSpeakerCount");
if (!config || !list || !count) return;
silenceState.speakerTurns = Array.isArray(speakerTurns) ? speakerTurns : silenceState.speakerTurns;
var previous = {};
silenceState.speakers.forEach(function (speaker) { previous[speaker.id] = speaker; });
var salvo = carregarNomesDeFalantes();
silenceState.speakers = speakerIds.map(function (id) {
var old = previous[id];
var salvoSpeaker = salvo[id] || {};
return {
id: id,
name: old && old.name ? old.name : speakerNameDefault(id),
active: !old || old.active !== false,
name: old && old.name ? old.name : (salvoSpeaker.name || speakerNameDefault(id)),
active: old ? old.active !== false : salvoSpeaker.active !== false,
};
});
count.textContent = silenceState.speakers.length +
@@ -2806,13 +2889,19 @@ function renderSpeakerConfiguration(speakerIds, speakerTurns) {
input.placeholder = "Nome do falante";
input.maxLength = 120;
input.setAttribute("aria-label", "Nome de " + speaker.id);
input.addEventListener("input", function () { silenceState.speakers[index].name = input.value.trim() || speaker.id; });
input.addEventListener("input", function () {
silenceState.speakers[index].name = input.value.trim() || speaker.id;
salvarNomesDeFalantes();
});
var activeLabel = document.createElement("label");
activeLabel.className = "checkbox-field speaker-active-field";
var active = document.createElement("input");
active.type = "checkbox";
active.checked = speaker.active;
active.addEventListener("change", function () { silenceState.speakers[index].active = active.checked; });
active.addEventListener("change", function () {
silenceState.speakers[index].active = active.checked;
salvarNomesDeFalantes();
});
var activeText = document.createElement("span");
activeText.textContent = "Incluir no vídeo";
activeLabel.appendChild(active);
@@ -2824,6 +2913,12 @@ function renderSpeakerConfiguration(speakerIds, speakerTurns) {
list.appendChild(row);
});
config.hidden = silenceState.speakers.length === 0;
var refineConfig = document.getElementById("refinarSpeakerConfig");
if (refineConfig && config.id !== refineConfig.id) {
renderSpeakerConfiguration(speakerIds, speakerTurns, {
config: "refinarSpeakerConfig", list: "refinarSpeakerList", count: "refinarSpeakerCount"
});
}
}
function speakerAudioFileUrl(filePath) {
@@ -3012,6 +3107,7 @@ function silenceGenerateJson() {
pathCell.textContent = out;
pathCell.title = out;
document.getElementById("silenceJsonCard").hidden = false;
document.getElementById("jsonFileActions").hidden = false;
document.getElementById("silencePlanInfo").textContent =
"Envie este arquivo a um agente de IA externo. Ele deve devolver o plano de edição em JSON para o passo 5.";