/* MCP for Adobe Premiere Pro - CEP Plugin Main Script
* Polls a temp directory for command files (.jsx), executes them
* in Premiere Pro's ExtendScript engine, and writes results back. */
var cs = new CSInterface();
var bridgeRunning = false;
var pollInterval = null;
var commandCount = 0;
var tempDir = "";
var POLL_MS = 200;
var HEARTBEAT_MS = 1000;
var heartbeatInterval = null;
// ---- Logging ----
function log(msg, cls) {
var el = document.getElementById("log");
var entry = document.createElement("div");
entry.className = "log-entry " + (cls || "");
var ts = new Date().toLocaleTimeString();
entry.textContent = "[" + ts + "] " + msg;
el.appendChild(entry);
el.scrollTop = el.scrollHeight;
// Keep max 100 entries
while (el.children.length > 100) el.removeChild(el.firstChild);
}
// ---- Painel de progresso (task dock) ----
// 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 TIPOS_VIDEO_CHAVE = "schedule_light_tipos_video";
var tipoVideoAtualId = null;
var tiposVideoPadrao = [
{ id: "midia-social", nome: "Conteúdo para mídia social", duracao_maxima_segundos: 60, descricao: "Vídeo curto para interromper a rolagem, comunicar uma ideia e estimular uma ação.", canais: "Instagram Reels, TikTok, Shorts", proporcao: "9:16", ritmo: "agil", emocao: "Curiosidade e energia", storytelling: true, estrutura: "Gancho nos primeiros 2 segundos → desenvolvimento em blocos curtos → CTA direto.", cortes: "Cortes rápidos, remover pausas e repetições; preservar frases de impacto. Usar jump cuts com moderação.", broll: "Inserir imagens de apoio a cada mudança de ideia, preferindo planos que reforcem a fala.", texto: "Legendas sempre; destacar palavras-chave na tela e usar título curto. CTA visual no final.", som: "Voz sempre inteligível e à frente da trilha; música energética em volume baixo.", diretriz: "Priorize retenção e clareza. Cada corte deve manter o ritmo e reforçar uma única ideia por vez." },
{ id: "depoimento", nome: "Depoimento de cliente", duracao_maxima_segundos: 120, descricao: "Relato autêntico que gera identificação, confiança e prova social.", canais: "Site, redes sociais, apresentação comercial", proporcao: "adaptavel", ritmo: "equilibrado", emocao: "Empatia e confiança", storytelling: true, estrutura: "Problema vivido → experiência com a solução → transformação → recomendação.", cortes: "Cortar hesitações e desvios sem deixar a fala artificial; manter reações, pausas emocionais e frases espontâneas.", broll: "Cobrir cortes com cenas do cliente usando o produto, ambiente real ou detalhes que comprovem o relato.", texto: "Nome e contexto do depoente; destacar a transformação em frases curtas; legendas completas.", som: "Voz natural, limpa e próxima; trilha emocional discreta, crescendo apenas na transformação.", diretriz: "Proteja a autenticidade do depoimento. A emoção deve vir da fala e das evidências, não de efeitos excessivos." },
{ id: "entrevista", nome: "Entrevista", duracao_maxima_segundos: 600, descricao: "Conversa estruturada para extrair conhecimento, opinião ou história de uma pessoa.", canais: "YouTube, podcast em vídeo, cortes sociais", proporcao: "16:9", ritmo: "equilibrado", emocao: "Interesse e credibilidade", storytelling: true, estrutura: "Apresentação → perguntas essenciais → aprofundamento → síntese e encerramento.", cortes: "Remover redundâncias e silêncios longos; preservar respostas completas, escuta, reação e contexto da pergunta.", broll: "Usar planos de reação, detalhes do ambiente e imagens relacionadas ao tema para quebrar a monotonia.", texto: "Identificação do entrevistado, perguntas resumidas quando necessário, capítulos ou palavras-chave.", som: "Diálogo consistente entre os canais; reduzir ruído e nivelar vozes sem tirar naturalidade.", diretriz: "Mantenha a linha de raciocínio da conversa. Não cortar uma resposta de modo que altere o sentido ou a intenção do entrevistado." },
{ id: "institucional", nome: "Vídeo institucional", duracao_maxima_segundos: 180, descricao: "Apresentação da empresa, marca, cultura ou solução com percepção de valor.", canais: "Site, eventos, apresentações, LinkedIn", proporcao: "16:9", ritmo: "progressivo", emocao: "Segurança e inspiração", storytelling: true, estrutura: "Contexto ou propósito → pessoas e operação → diferenciais → prova → convite para conhecer.", cortes: "Ritmo elegante; evitar excesso de cortes. Alternar entrevistas, planos de ambiente e detalhes de operação.", broll: "B-roll planejado para mostrar pessoas, processos, produto e escala; toda imagem deve provar uma mensagem.", texto: "Títulos limpos, dados essenciais e assinatura da marca; evitar excesso de texto na tela.", som: "Trilha cinematográfica ou institucional discreta; voz firme, clara e com pausas bem preservadas.", diretriz: "Construa confiança e percepção de valor. Use consistência visual e sonora; efeitos só quando ajudarem a mensagem da marca." },
{ id: "educativo-storytelling", nome: "Educativo com storytelling", duracao_maxima_segundos: 240, descricao: "Explicação didática que ensina uma ideia por meio de uma história, caso ou problema concreto.", canais: "YouTube, curso, LinkedIn, redes sociais", proporcao: "adaptavel", ritmo: "progressivo", emocao: "Curiosidade e clareza", storytelling: true, estrutura: "Problema ou pergunta → história/exemplo → explicação → aplicação prática → resumo.", cortes: "Cortar repetições, mas manter a sequência pedagógica; inserir pausas após conceitos importantes.", broll: "Usar diagramas, tela, exemplos e imagens que tornem o conceito visível; sincronizar apoio com a explicação.", texto: "Título do conceito, palavras-chave e resumo final; legendas e marcadores de etapas.", som: "Voz em primeiro plano; trilha quase imperceptível; pequenos efeitos apenas para sinalizar transições.", diretriz: "Ensine uma coisa por vez. O espectador deve entender o conceito sem depender apenas de texto ou de efeitos visuais." }
];
function tipoVideoCompletar(tipo) {
var configuracoes = {
"midia-social": { categoria: "Comercial", objetivo: "Gerar retenção e ação", objetivo_secundario: "Comunicar uma ideia rapidamente", publico: "Público das redes sociais", mensagem: "Uma ideia clara por vídeo.", tipo_narrativa: "Problema → solução → benefício", foco_narrativo: "Gancho e CTA", intensidade: 4, tom: "Positivo", construcao_emocao: "Ritmo ágil, cortes de impacto, legendas e trilha energética.", duracao_minima: 15, prioridades: ["Retenção", "Clareza", "Mensagem", "Ritmo", "Estética"], usa_broll: true, usa_legendas: true, usa_textos: true, usa_graficos: false, usa_zoom: true, usa_musica: true, intensidade_musica: 25, prioridade_voz: "Muito alta", restricoes: "Evitar excesso de informação e cortes sem função.", qualidade: "A abertura prende atenção e a mensagem é compreendida sem áudio." },
"depoimento": { categoria: "Depoimento", objetivo: "Gerar credibilidade e confiança", objetivo_secundario: "Criar identificação emocional", publico: "Clientes em potencial", mensagem: "A experiência real comprova o valor da solução.", tipo_narrativa: "História pessoal", foco_narrativo: "Problema → experiência → transformação → recomendação", intensidade: 3, tom: "Íntimo", construcao_emocao: "Preservar pausas naturais, reações e imagens reais; usar trilha discreta.", duracao_minima: 30, prioridades: ["Autenticidade", "Clareza", "Conexão emocional", "Ritmo", "Estética"], usa_broll: true, usa_legendas: true, usa_textos: true, usa_graficos: false, usa_zoom: false, usa_musica: true, intensidade_musica: 20, prioridade_voz: "Muito alta", restricoes: "Não alterar o sentido das frases nem deixar o depoimento artificial.", qualidade: "A pessoa parece verdadeira e o resultado do relato fica evidente." },
"entrevista": { categoria: "Entrevista", objetivo: "Transmitir conhecimento e autoridade", objetivo_secundario: "Organizar a conversa por temas", publico: "Pessoas interessadas no assunto", mensagem: "O conteúdo da conversa é compreendido com contexto.", tipo_narrativa: "Pergunta e resposta", foco_narrativo: "Pergunta → resposta → aprofundamento", intensidade: 2, tom: "Informativo", construcao_emocao: "Reações, escuta e pausas que preservem a naturalidade da conversa.", duracao_minima: 60, prioridades: ["Clareza", "Conteúdo", "Contexto", "Ritmo", "Estética"], usa_broll: true, usa_legendas: true, usa_textos: true, usa_graficos: false, usa_zoom: true, usa_musica: false, intensidade_musica: 10, prioridade_voz: "Muito alta", restricoes: "Não cortar frases de modo que prejudique o raciocínio do entrevistado.", qualidade: "As respostas mantêm contexto, lógica e informação relevante." },
"institucional": { categoria: "Institucional", objetivo: "Apresentar valor e confiança na marca", objetivo_secundario: "Mostrar pessoas, processo e diferenciais", publico: "Clientes, parceiros e equipe", mensagem: "A marca entrega valor de forma consistente.", tipo_narrativa: "Jornada / transformação", foco_narrativo: "Propósito → pessoas → operação → prova", intensidade: 4, tom: "Inspirador", construcao_emocao: "Trilha progressiva, imagens de pessoas e planos de operação.", duracao_minima: 45, prioridades: ["Confiança", "Mensagem", "Narrativa", "Estética", "Ritmo"], usa_broll: true, usa_legendas: true, usa_textos: true, usa_graficos: true, usa_zoom: false, usa_musica: true, intensidade_musica: 35, prioridade_voz: "Alta", restricoes: "Evitar efeitos decorativos que não reforcem a mensagem da marca.", qualidade: "O vídeo transmite profissionalismo, clareza e percepção de valor." },
"educativo-storytelling": { categoria: "Educativo", objetivo: "Ensinar um assunto com clareza", objetivo_secundario: "Aumentar retenção e aplicação prática", publico: "Pessoas que precisam aprender o assunto", mensagem: "O espectador consegue entender e aplicar o conceito.", tipo_narrativa: "Explicação passo a passo", foco_narrativo: "Problema → exemplo → explicação → aplicação", intensidade: 2, tom: "Informativo", construcao_emocao: "Curiosidade inicial, exemplos visuais e pausas após conceitos importantes.", duracao_minima: 60, prioridades: ["Clareza", "Organização", "Didática", "Retenção", "Estética"], usa_broll: true, usa_legendas: true, usa_textos: true, usa_graficos: true, usa_zoom: true, usa_musica: false, intensidade_musica: 5, prioridade_voz: "Muito alta", restricoes: "Não cortar exemplos ou transições que prejudiquem a explicação.", qualidade: "O conteúdo fica fácil de acompanhar, revisar e aplicar." }
}[tipo.id] || {};
var completo = Object.assign({ ativo: true, categoria: "Personalizado", objetivo: "", objetivo_secundario: "", publico: "", mensagem: "", tipo_narrativa: "História pessoal", foco_narrativo: "", intensidade: 3, tom: "Positivo", construcao_emocao: "", duracao_minima: 0, prioridades: ["Clareza", "Mensagem", "Ritmo", "Conexão emocional", "Estética"], usa_broll: false, usa_legendas: true, usa_textos: false, usa_graficos: false, usa_zoom: false, usa_musica: false, intensidade_musica: 20, prioridade_voz: "Alta", restricoes: "", qualidade: "" }, configuracoes, tipo);
return completo;
}
function taskEl(id) { return document.getElementById(id); }
function formatClock(ms) {
var total = Math.max(0, Math.round(ms / 1000));
var mm = String(Math.floor(total / 60)).padStart(2, "0");
var ss = String(total % 60).padStart(2, "0");
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) {
if (taskState.clearTimer) { clearTimeout(taskState.clearTimer); taskState.clearTimer = null; }
taskState.active = true;
taskState.startedAt = Date.now();
taskState.mirror = mirror || null;
taskState.originTab = activeTab();
taskState.errorText = "";
taskMoveToOriginTab();
var dock = taskEl("taskDock");
dock.removeAttribute("data-result");
dock.hidden = false;
taskEl("taskTitle").textContent = title;
taskEl("taskStage").textContent = stage || "Preparando…";
taskEl("taskPct").textContent = "";
taskEl("taskElapsed").textContent = "00:00";
taskEl("taskTrack").setAttribute("data-mode", "indeterminate");
taskEl("taskFill").style.width = "0%";
taskEl("taskCopy").hidden = true;
taskEl("taskErrorDetails").hidden = true;
taskEl("taskErrorDetails").textContent = "";
hideToast();
if (taskState.timer) clearInterval(taskState.timer);
taskState.timer = setInterval(function () {
taskEl("taskElapsed").textContent = formatClock(Date.now() - taskState.startedAt);
}, 1000);
}
// info: { stage, pct, detail }
function taskUpdate(info) {
if (!taskState.active) return;
if (taskState.mirror) { try { taskState.mirror(info); } catch (e) {} }
var stage = info.stage || "";
var detail = info.detail || "";
taskEl("taskStage").textContent = detail ? stage + " · " + detail : stage;
if (typeof info.pct === "number" && isFinite(info.pct)) {
taskEl("taskTrack").setAttribute("data-mode", "determinate");
taskEl("taskFill").style.width = Math.max(0, Math.min(100, info.pct)) + "%";
taskEl("taskPct").textContent = Math.round(info.pct) + "%";
} else {
taskEl("taskTrack").setAttribute("data-mode", "indeterminate");
taskEl("taskPct").textContent = "";
}
}
function taskEnd(ok, message, technicalDetails) {
if (taskState.timer) { clearInterval(taskState.timer); taskState.timer = null; }
taskState.active = false;
var dock = taskEl("taskDock");
dock.setAttribute("data-result", ok ? "ok" : "err");
taskEl("taskTrack").setAttribute("data-mode", "determinate");
taskEl("taskFill").style.width = "100%";
taskEl("taskStage").textContent = message || (ok ? "Concluído" : "Falhou");
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;
// 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 () {
if (!taskState.active) taskEl("taskDock").hidden = true;
}, 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) {
var el = document.getElementById("toast");
if (!el) return;
el.setAttribute("data-kind", kind);
document.getElementById("toastIcon").textContent = kind === "ok" ? "✓" : "!";
document.getElementById("toastText").textContent = text;
el.hidden = false;
}
function hideToast() {
var el = document.getElementById("toast");
if (el) el.hidden = true;
}
// Botão com spinner embutido: o usuário vê que aquele botão específico disparou
// o processo, sem precisar caçar no log.
function setBusy(id, busy) {
var btn = document.getElementById(id);
if (!btn) return;
btn.classList.toggle("is-busy", !!busy);
btn.disabled = !!busy;
}
// ---- Estados dos passos da aba "Editar vídeo" ----
// locked = ainda não dá pra usar | ready = pode usar | running | done | error
function setStep(n, state, chipText) {
var step = document.getElementById("step" + n);
var chip = document.getElementById("step" + n + "Chip");
if (!step) return;
step.setAttribute("data-state", state);
if (chip && chipText) chip.textContent = chipText;
else if (chip) {
chip.textContent =
state === "locked" ? "Bloqueado" :
state === "ready" ? "Pronto" :
state === "running" ? "Rodando…" :
state === "done" ? "Concluído" : "Erro";
}
}
function stepState(n) {
var step = document.getElementById("step" + n);
return step ? step.getAttribute("data-state") : "locked";
}
// ---- Tabs ----
function switchTab(name) {
var tabs = [
{ key: "silence", panel: "tabSilence", btn: "tabBtnSilence" },
{ key: "scanner", panel: "tabScanner", btn: "tabBtnScanner" },
{ key: "retakes", panel: "tabRetakes", btn: "tabBtnRetakes" },
{ key: "analises", panel: "tabAnalises", btn: "tabBtnAnalises" },
{ key: "tipos-video", panel: "tabTiposVideo", btn: "tabBtnTiposVideo" },
{ key: "bridge", panel: "tabBridge", btn: "tabBtnBridge" },
{ key: "models", panel: "tabModels", btn: "tabBtnModels" },
{ key: "settings", panel: "tabSettings", btn: "tabBtnSettings" },
];
tabs.forEach(function (t) {
var active = t.key === name;
var panel = document.getElementById(t.panel);
var btn = document.getElementById(t.btn);
if (!panel || !btn) return;
panel.hidden = !active;
btn.classList.toggle("active", active);
btn.setAttribute("aria-selected", String(active));
});
if (name === "models") renderModelList();
if (name === "silence") { syncTranscribeModelSelect(); syncLanguageInfo(); }
if (name === "settings") renderEditorPersonalities();
if (name === "scanner") { renderScannerTranscriptionOptions(); loadScannerProfile(); renderScannerHuggingFaceStatus(); }
if (name === "retakes") retakesAoAbrir();
if (name === "analises") analisesAoAbrir();
if (name === "tipos-video") tiposVideoAoAbrir();
}
// ---- Catálogo de tipos de vídeo -------------------------------------------
// A primeira versão usa o armazenamento local do painel. O documento salvo já
// tem formato estável e pode ser migrado para o Schedule Light sem alterar o
// contrato editorial enviado para a IA.
function tiposVideoLer() {
try {
var salvo = JSON.parse(localStorage.getItem(TIPOS_VIDEO_CHAVE) || "null");
if (Array.isArray(salvo) && salvo.length) return salvo.map(tipoVideoCompletar);
var iniciais = tiposVideoPadrao.slice().map(tipoVideoCompletar);
tiposVideoSalvarNoCatalogo(iniciais);
return iniciais;
} catch (_) { return tiposVideoPadrao.slice().map(tipoVideoCompletar); }
}
function tiposVideoSalvarNoCatalogo(lista) {
localStorage.setItem(TIPOS_VIDEO_CHAVE, JSON.stringify(lista));
}
function tiposVideoAoAbrir() {
tiposVideoRenderizarLista();
if (!tipoVideoAtualId) tipoVideoNovo();
}
function tiposVideoRenderizarLista() {
var lista = document.getElementById("tiposVideoLista");
if (!lista) return;
lista.innerHTML = "";
tiposVideoLer().forEach(function (tipo) {
var item = document.createElement("div");
item.className = "video-type-row" + (tipo.id === tipoVideoAtualId ? " is-selected" : "");
item.setAttribute("role", "listitem");
item.innerHTML = '
';
item.querySelector("strong").textContent = tipo.nome;
item.querySelector("span").textContent = tipo.descricao;
item.querySelector(".video-type-meta").textContent = tipo.duracao_maxima_segundos + " s · " + tipo.proporcao + " · " + tipo.canais;
item.querySelector("button").onclick = function () { tipoVideoEditar(tipo.id); };
lista.appendChild(item);
});
}
function tipoVideoNovo() {
tipoVideoAtualId = null;
tipoVideoPreencher(tipoVideoCompletar({ id: "", nome: "", duracao_maxima_segundos: 60, descricao: "", canais: "", proporcao: "9:16", ritmo: "equilibrado", emocao: "", storytelling: true, estrutura: "", cortes: "", broll: "", texto: "", som: "", diretriz: "" }));
document.getElementById("tipoVideoEditorTitulo").textContent = "Novo tipo de vídeo";
document.getElementById("tipoVideoEditorStatus").textContent = "Rascunho";
document.getElementById("tipoVideoEditorCard").hidden = false;
tiposVideoRenderizarLista();
}
function tipoVideoEditar(id) {
var tipo = tiposVideoLer().filter(function (item) { return item.id === id; })[0];
if (!tipo) return;
tipoVideoAtualId = id;
tipoVideoPreencher(tipo);
document.getElementById("tipoVideoEditorTitulo").textContent = "Editar tipo de vídeo";
document.getElementById("tipoVideoEditorStatus").textContent = "Cadastrado";
document.getElementById("tipoVideoEditorCard").hidden = false;
tiposVideoRenderizarLista();
}
function tipoVideoPreencher(tipo) {
tipo = tipoVideoCompletar(tipo);
["nome", "descricao", "canais", "proporcao", "ritmo", "emocao", "estrutura", "cortes", "broll", "texto", "som", "diretriz", "categoria", "objetivo", "objetivo_secundario", "publico", "mensagem", "tipo_narrativa", "foco_narrativo", "tom", "construcao_emocao", "restricoes", "qualidade", "prioridade_voz"].forEach(function (campo) {
var elemento = document.getElementById("tipoVideo" + campo.charAt(0).toUpperCase() + campo.slice(1));
if (elemento) elemento.value = tipo[campo] || "";
});
document.getElementById("tipoVideoDuracao").value = tipo.duracao_maxima_segundos || "";
document.getElementById("tipoVideoDuracaoMinima").value = tipo.duracao_minima || "";
document.getElementById("tipoVideoStorytelling").checked = !!tipo.storytelling;
document.getElementById("tipoVideoAtivo").checked = tipo.ativo !== false;
document.getElementById("tipoVideoIntensidade").value = tipo.intensidade || 3;
document.getElementById("tipoVideoIntensidadeMusica").value = tipo.intensidade_musica || 0;
document.getElementById("tipoVideoUsaBroll").checked = !!tipo.usa_broll;
document.getElementById("tipoVideoUsaLegendas").checked = !!tipo.usa_legendas;
document.getElementById("tipoVideoUsaTextos").checked = !!tipo.usa_textos;
document.getElementById("tipoVideoUsaGraficos").checked = !!tipo.usa_graficos;
document.getElementById("tipoVideoUsaZoom").checked = !!tipo.usa_zoom;
document.getElementById("tipoVideoUsaMusica").checked = !!tipo.usa_musica;
tipoVideoRenderizarPrioridades(tipo.prioridades);
tipoVideoAtualizarIntensidade();
tipoVideoAtualizarIntensidadeMusica();
}
function tipoVideoRenderizarPrioridades(prioridades) {
var lista = document.getElementById("tipoVideoPrioridades");
lista.innerHTML = "";
(prioridades || []).slice(0, 5).forEach(function (valor, indice) {
var linha = document.createElement("div");
linha.className = "priority-row";
linha.innerHTML = '' + (indice + 1) + '';
linha.querySelector("input").value = valor;
lista.appendChild(linha);
});
}
function tipoVideoPrioridadesDoFormulario() {
return Array.prototype.slice.call(document.querySelectorAll("#tipoVideoPrioridades .priority-input")).map(function (input) { return input.value.trim(); }).filter(Boolean);
}
function tipoVideoAtualizarIntensidade() {
var valor = document.getElementById("tipoVideoIntensidade");
var saida = document.getElementById("tipoVideoIntensidadeValor");
if (valor && saida) saida.textContent = valor.value + " / 5";
}
function tipoVideoAtualizarIntensidadeMusica() {
var valor = document.getElementById("tipoVideoIntensidadeMusica");
var saida = document.getElementById("tipoVideoIntensidadeMusicaValor");
if (valor && saida) saida.textContent = valor.value + "%";
}
function tipoVideoDoFormulario() {
var nome = document.getElementById("tipoVideoNome").value.trim();
if (!nome) throw new Error("Informe o nome do tipo de vídeo.");
var base = tipoVideoAtualId ? tiposVideoLer().filter(function (item) { return item.id === tipoVideoAtualId; })[0] : null;
return {
id: tipoVideoAtualId || (nome.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-" + Date.now()),
nome: nome,
descricao: document.getElementById("tipoVideoDescricao").value.trim(),
ativo: document.getElementById("tipoVideoAtivo").checked,
categoria: document.getElementById("tipoVideoCategoria").value,
objetivo: document.getElementById("tipoVideoObjetivo").value.trim(),
objetivo_secundario: document.getElementById("tipoVideoObjetivoSecundario").value.trim(),
publico: document.getElementById("tipoVideoPublico").value.trim(),
mensagem: document.getElementById("tipoVideoMensagem").value.trim(),
tipo_narrativa: document.getElementById("tipoVideoTipoNarrativa").value,
foco_narrativo: document.getElementById("tipoVideoFocoNarrativo").value.trim(),
intensidade: Number(document.getElementById("tipoVideoIntensidade").value) || 3,
tom: document.getElementById("tipoVideoTom").value,
construcao_emocao: document.getElementById("tipoVideoConstrucaoEmocao").value.trim(),
duracao_maxima_segundos: Number(document.getElementById("tipoVideoDuracao").value) || 0,
duracao_minima: Number(document.getElementById("tipoVideoDuracaoMinima").value) || 0,
canais: document.getElementById("tipoVideoCanais").value.trim(),
proporcao: document.getElementById("tipoVideoProporcao").value,
ritmo: document.getElementById("tipoVideoRitmo").value,
emocao: document.getElementById("tipoVideoEmocao").value.trim(),
storytelling: document.getElementById("tipoVideoStorytelling").checked,
estrutura: document.getElementById("tipoVideoEstrutura").value.trim(),
cortes: document.getElementById("tipoVideoCortes").value.trim(),
broll: document.getElementById("tipoVideoBroll").value.trim(),
texto: document.getElementById("tipoVideoTexto").value.trim(),
som: document.getElementById("tipoVideoSom").value.trim(),
diretriz: document.getElementById("tipoVideoDiretriz").value.trim(),
prioridades: tipoVideoPrioridadesDoFormulario(),
usa_broll: document.getElementById("tipoVideoUsaBroll").checked,
usa_legendas: document.getElementById("tipoVideoUsaLegendas").checked,
usa_textos: document.getElementById("tipoVideoUsaTextos").checked,
usa_graficos: document.getElementById("tipoVideoUsaGraficos").checked,
usa_zoom: document.getElementById("tipoVideoUsaZoom").checked,
usa_musica: document.getElementById("tipoVideoUsaMusica").checked,
intensidade_musica: Number(document.getElementById("tipoVideoIntensidadeMusica").value) || 0,
prioridade_voz: document.getElementById("tipoVideoPrioridadeVoz").value,
restricoes: document.getElementById("tipoVideoRestricoes").value.trim(),
qualidade: document.getElementById("tipoVideoQualidade").value.trim(),
criado_em: base && base.criado_em ? base.criado_em : new Date().toISOString(),
atualizado_em: new Date().toISOString()
};
}
function tipoVideoSalvar() {
try {
var item = tipoVideoDoFormulario();
var lista = tiposVideoLer();
var indice = lista.findIndex(function (tipo) { return tipo.id === item.id; });
if (indice >= 0) lista[indice] = item; else lista.push(item);
tiposVideoSalvarNoCatalogo(lista);
tipoVideoAtualId = item.id;
document.getElementById("tipoVideoEditorStatus").textContent = "Salvo";
document.getElementById("tiposVideoStatus").textContent = "Catálogo salvo localmente · " + lista.length + " tipo(s) cadastrado(s).";
tiposVideoRenderizarLista();
showToast("ok", "Tipo de vídeo salvo no catálogo.");
} catch (erro) { showToast("err", erro.message); }
}
function tipoVideoCancelar() {
tipoVideoAtualId = null;
document.getElementById("tipoVideoEditorCard").hidden = true;
tiposVideoRenderizarLista();
}
function tipoVideoCopiarJson() {
try {
var json = JSON.stringify({ tipo_de_video: tipoVideoDoFormulario() }, null, 2);
var copia = childProcess.spawn("pbcopy");
copia.stdin.end(json);
showToast("ok", "Configuração copiada em JSON para a IA.");
} catch (erro) { showToast("err", erro.message); }
}
// ---- Análises ---------------------------------------------------------------
// Aba que lê o banco de análises (.jhonny/analises.db) já produzido pelo
// Scanner e mostra/copiar os dois J-SOM: som (transcrição por cena) e
// imagem (evidências visuais). A leitura acontece na engine, via script
// dedicado; a aba nunca escreve no banco.
var ANALISES_ENGINE_SCRIPT = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code/engine/gerar_relatorio_de_analises.py";
var ANALISES_DB_PATH = "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/.jhonny/analises.db";
var analisesRelatorioPath = null;
function analisesAoAbrir() {
document.getElementById("analisesCaminho").textContent = ANALISES_DB_PATH;
}
function analisesFormatarTempo(segundos) {
var s = Number(segundos) || 0;
var min = Math.floor(s / 60), resto = (s - min * 60).toFixed(1);
return min + ":" + (resto < 10 ? "0" : "") + resto;
}
function analisesCarregar() {
var stamp = new Date().toISOString().replace(/[:.]/g, "-");
analisesRelatorioPath = path.join(os.tmpdir(), "jhonny-analises-" + stamp + ".json");
setBusy("btnAnalisesCarregar", true); taskStart("Análises", "Lendo o banco de análises", null);
var child = childProcess.spawn(SCANNER_PYTHON_BIN, [ANALISES_ENGINE_SCRIPT, ANALISES_DB_PATH, analisesRelatorioPath],
{ cwd: "/Volumes/Merongo/SISTEMAS/GENIAL SISTEMAS/Jhonny/code", stdio: ["ignore", "pipe", "pipe"] });
var stderr = "", stdout = "";
child.stdout.on("data", function (chunk) { stdout += chunk.toString(); });
child.stderr.on("data", function (chunk) { stderr += chunk.toString(); });
child.on("error", function (error) { setBusy("btnAnalisesCarregar", false); taskEnd(false, "Análises indisponíveis", error.stack || error.message); });
child.on("close", function (code) {
setBusy("btnAnalisesCarregar", false);
if (code !== 0 || !fs.existsSync(analisesRelatorioPath)) {
taskEnd(false, "Leitura do banco falhou", stderr || stdout || "Não foi possível gerar o relatório.");
return;
}
taskEnd(true, "Relatório carregado do banco");
analisesRender(JSON.parse(fs.readFileSync(analisesRelatorioPath, "utf-8")));
});
}
function analisesRender(relatorio) {
var video = relatorio.video || {};
var cenas = relatorio.cenas || [];
var falas = cenas.reduce(function (n, c) { return n + (c.falas || []).length; }, 0);
var evidencias = cenas.reduce(function (n, c) {
var resumo = c.resumo_de_evidencias || {};
return n + Object.keys(resumo).reduce(function (m, k) { return m + resumo[k]; }, 0);
}, 0);
document.getElementById("analisesStatus").textContent = "Carregado";
document.getElementById("analisesVideoNome").textContent = video.nome || "(sem nome)";
document.getElementById("analisesResumo").textContent =
cenas.length + " cenas · " + falas + " falas · " + evidencias + " evidências visuais";
document.getElementById("analisesResumoCard").hidden = false;
document.getElementById("btnAnalisesAbrir").disabled = false;
document.getElementById("btnAnalisesCopiar").disabled = false;
var lista = document.getElementById("analisesCenas");
lista.innerHTML = "";
cenas.forEach(function (cena) {
var bloco = document.createElement("div");
bloco.className = "analise-cena";
bloco.setAttribute("role", "listitem");
var titulo = cena.cena_registrada ? "" : " (sem cena gravada)";
var tipos = Object.keys(cena.resumo_de_evidencias || {})
.sort().map(function (k) { return k + " " + cena.resumo_de_evidencias[k]; }).join(", ") || "—";
var falasHtml = (cena.falas || []).map(function (fala) {
return "