Files
jhonny-editor/code/cep-plugin/index.html
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

408 lines
21 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MCP for Adobe Premiere Pro</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="panel-shell">
<header class="panel-header">
<div class="brand-mark" aria-hidden="true"><span>M</span></div>
<div class="brand-copy">
<h1>MCP for Adobe Premiere Pro</h1>
<p>Conexão local com o Premiere</p>
</div>
<div class="header-status" id="headerStatus" data-state="waiting" title="Status da conexão">
<span class="header-status-dot" aria-hidden="true"></span>
<span id="headerStatusText">Iniciando…</span>
</div>
</header>
<nav class="tab-bar" role="tablist" aria-label="Painel">
<button class="tab-button active" id="tabBtnSilence" role="tab" aria-selected="true" aria-controls="tabSilence" onclick="switchTab('silence')" type="button">
<span class="tab-icon" aria-hidden="true">✂</span>Editar vídeo
</button>
<button class="tab-button" id="tabBtnBridge" role="tab" aria-selected="false" aria-controls="tabBridge" onclick="switchTab('bridge')" type="button">
<span class="tab-icon" aria-hidden="true">⇄</span>Conexão
</button>
<button class="tab-button" id="tabBtnModels" role="tab" aria-selected="false" aria-controls="tabModels" onclick="switchTab('models')" type="button">
<span class="tab-icon" aria-hidden="true">◈</span>Modelos
</button>
<button class="tab-button" id="tabBtnSettings" role="tab" aria-selected="false" aria-controls="tabSettings" onclick="switchTab('settings')" type="button">
<span class="tab-icon" aria-hidden="true">⚙</span>Ajustes
</button>
</nav>
<!-- Painel de progresso global: tudo que está rodando aparece aqui, no topo,
em vez de ficar escondido no log lá embaixo. -->
<section class="task-dock" id="taskDock" role="status" aria-live="polite" hidden>
<div class="task-head">
<span class="task-spinner" id="taskSpinner" aria-hidden="true"></span>
<strong class="task-title" id="taskTitle">Processando…</strong>
<span class="task-elapsed" id="taskElapsed">00:00</span>
</div>
<div class="progress-track" id="taskTrack" data-mode="indeterminate">
<div class="progress-fill" id="taskFill" style="width:0%"></div>
</div>
<div class="task-foot">
<span class="task-stage" id="taskStage">Preparando…</span>
<span class="task-pct" id="taskPct"></span>
</div>
</section>
<div class="toast" id="toast" role="status" aria-live="polite" hidden>
<span class="toast-icon" id="toastIcon" aria-hidden="true">✓</span>
<span class="toast-text" id="toastText"></span>
<button type="button" class="toast-close" onclick="hideToast()" aria-label="Fechar aviso">✕</button>
</div>
<!-- ============================= EDITAR VÍDEO ============================= -->
<div class="tab-panel" id="tabSilence" role="tabpanel" aria-labelledby="tabBtnSilence">
<p class="tab-intro">Siga os passos de cima para baixo. Cada passo libera o seguinte.</p>
<section class="step" id="step1" data-state="ready">
<div class="step-head">
<span class="step-number" aria-hidden="true">1</span>
<div class="step-title">
<h2>Escolher o vídeo</h2>
<p>Lê o primeiro clipe da sequência aberta no Premiere.</p>
</div>
<span class="step-chip" id="step1Chip">Comece aqui</span>
</div>
<div class="step-body">
<button id="btnDetectClip" class="button button-primary" onclick="silenceDetectClip()" type="button">Detectar sequência ativa</button>
<dl class="kv" id="silenceClipCard" hidden>
<div><dt>Sequência</dt><dd id="clipSequenceName">—</dd></div>
<div><dt>Arquivo</dt><dd id="clipFileName">—</dd></div>
<div><dt>Caminho</dt><dd class="kv-path" id="clipMediaPath">—</dd></div>
</dl>
<p class="field-help" id="silenceClipInfo">Abra a sequência desejada no Premiere e clique em Detectar.</p>
</div>
</section>
<section class="step" id="step2" data-state="locked">
<div class="step-head">
<span class="step-number" aria-hidden="true">2</span>
<div class="step-title">
<h2>Transcrever a fala</h2>
<p>Gera o texto com marcação de tempo usando o Whisper local.</p>
</div>
<span class="step-chip" id="step2Chip">Bloqueado</span>
</div>
<div class="step-body">
<div class="callout callout-info" id="silenceCachedSection" hidden>
<strong>Este vídeo já foi transcrito antes</strong>
<p>Reaproveite e pule direto para o plano de cortes — leva segundos em vez de minutos.</p>
<label class="field-label" for="silenceCachedSelect">Transcrição salva</label>
<select id="silenceCachedSelect" class="select-field"></select>
<button id="btnUseCachedTranscript" class="button button-primary" onclick="silenceUseCachedTranscript()" type="button">Usar esta transcrição</button>
</div>
<div class="field-grid">
<div>
<label class="field-label" for="silenceModel">Modelo</label>
<select id="silenceModel" class="select-field">
<option value="small" selected>small (recomendado)</option>
<option value="base">base (mais rápido)</option>
<option value="medium">medium (mais preciso)</option>
</select>
</div>
<div>
<span class="field-label">Idioma</span>
<p class="field-static" id="silenceLanguageInfo">Português</p>
</div>
</div>
<label class="checkbox-field">
<input type="checkbox" id="silenceDiarize">
<span>Detectar quem fala logo após transcrever</span>
</label>
<div class="mini-action" id="silenceRemovalRow">
<div>
<label class="checkbox-field" style="border:none;padding:0;background:none">
<input type="checkbox" id="silenceRemovalEnabled">
<span>Remoção de silêncios</span>
</label>
<span>Só grava a preferência no JSON — não corta nada aqui. A IA decide os cortes usando esse valor.</span>
</div>
<div style="display:flex;align-items:center;gap:6px;flex:0 0 auto">
<label class="field-label" for="silenceRemovalMinDuration" style="margin:0">Mín. (s)</label>
<input type="number" id="silenceRemovalMinDuration" class="select-field" style="width:64px" step="0.1" min="0" value="0.8">
</div>
</div>
<button id="btnTranscribe" class="button button-primary" onclick="silenceTranscribe()" type="button" disabled>Transcrever</button>
<p class="field-help" id="silenceCacheInfo"></p>
</div>
</section>
<section class="step step-optional" id="step3" data-state="locked">
<div class="step-head">
<span class="step-number" aria-hidden="true">3</span>
<div class="step-title">
<h2>Enriquecer a transcrição <span class="badge-optional">opcional</span></h2>
<p>Dados extras que deixam o corte por IA mais preciso.</p>
</div>
<span class="step-chip" id="step3Chip">Bloqueado</span>
</div>
<div class="step-body">
<div class="mini-action">
<div>
<strong>Quem fala</strong>
<span id="silenceDiarizeInfo">Separa os locutores por trecho. Precisa do token Hugging Face.</span>
</div>
<button id="btnDiarize" class="button" onclick="silenceDiarizeSpeakers()" type="button" disabled>Detectar</button>
</div>
<div class="mini-action">
<div>
<strong>Métricas de voz</strong>
<span id="silenceVoiceFeaturesInfo">Pitch, energia, velocidade de fala e pausas de cada trecho.</span>
</div>
<button id="btnVoiceFeatures" class="button" onclick="silenceComputeVoiceFeatures()" type="button" disabled>Calcular</button>
</div>
</div>
</section>
<section class="step" id="step4" data-state="locked">
<div class="step-head">
<span class="step-number" aria-hidden="true">4</span>
<div class="step-title">
<h2>Gerar arquivo JSON</h2>
<p>Reúne transcrição, tempos, locutores, métricas de voz e suas configurações de edição. Nada é cortado — o arquivo vai para um agente de IA externo analisar.</p>
</div>
<span class="step-chip" id="step4Chip">Bloqueado</span>
</div>
<div class="step-body">
<button id="btnBuildPlan" class="button button-primary" onclick="silenceGenerateJson()" type="button" disabled>Gerar arquivo JSON</button>
<dl class="kv" id="silenceJsonCard" hidden>
<div><dt>Trechos</dt><dd id="jsonSegmentCount">—</dd></div>
<div><dt>Arquivo</dt><dd class="kv-path" id="jsonOutputPath">—</dd></div>
</dl>
<p class="field-help" id="silencePlanInfo"></p>
</div>
</section>
<section class="step" id="step5" data-state="locked">
<div class="step-head">
<span class="step-number" aria-hidden="true">5</span>
<div class="step-title">
<h2>Aplicar na timeline</h2>
<p>Selecione o plano de edição que a IA devolveu (cortes, zooms, textos, marcadores) e execute na sequência.</p>
</div>
<span class="step-chip" id="step5Chip">Bloqueado</span>
</div>
<div class="step-body">
<label class="field-label" for="editorialPlanPath">Arquivo do plano (JSON devolvido pela IA)</label>
<div class="file-field">
<input type="text" id="editorialPlanPath" spellcheck="false" autocomplete="off" placeholder="/caminho/para/plano.json">
<button type="button" class="button button-ghost" id="btnPickPlanFile" onclick="pickEditorialPlanFile()">Procurar…</button>
</div>
<div class="callout callout-warn" id="silenceApplyInfo">
<strong>Sem desfazer</strong>
<p>Esta versão do Premiere não expõe undo para o painel. Um backup da sequência é criado automaticamente antes de aplicar.</p>
</div>
<button id="btnApplyEditorialActions" class="button button-danger" onclick="applyEditorialActions()" type="button" disabled>Aplicar plano na timeline</button>
</div>
</section>
<details class="log-details">
<summary>Detalhes técnicos <span class="log-hint">(o passo a passo completo)</span></summary>
<div id="silenceLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log da edição"></div>
</details>
</div>
<!-- ============================== CONEXÃO ============================== -->
<div class="tab-panel" id="tabBridge" role="tabpanel" aria-labelledby="tabBtnBridge" hidden>
<section class="status-panel" role="status" aria-live="polite" aria-atomic="true">
<div class="status-indicator" aria-hidden="true">
<div class="status-ring"><div class="status-dot waiting" id="statusDot"></div></div>
</div>
<div class="status-copy">
<span class="section-label">Status da ponte</span>
<strong id="statusText">Iniciando conector…</strong>
<span id="statusDetail">Verificando o Premiere Pro</span>
</div>
<div class="command-stat">
<strong id="cmdCount">0</strong>
<span>comandos</span>
</div>
</section>
<div class="action-row">
<button id="btnStart" class="button button-primary" onclick="startBridge()" type="button" aria-controls="log">
<svg viewBox="0 0 20 20" aria-hidden="true"><path class="fill-icon" d="m7 5 8 5-8 5V5Z"/></svg>
Iniciar
</button>
<button id="btnStop" class="button button-danger" onclick="stopBridge()" type="button" aria-controls="log" disabled>
<svg viewBox="0 0 20 20" aria-hidden="true"><rect class="fill-icon" x="6" y="6" width="8" height="8" rx="1"/></svg>
Parar
</button>
</div>
<section class="card" aria-labelledby="connectionCenterTitle">
<div class="card-head">
<h2 id="connectionCenterTitle">Pronto para editar?</h2>
<button class="link-button" type="button" onclick="refreshConnectionCenter()" aria-controls="connectionChecks">Verificar</button>
</div>
<ul class="connection-checks" id="connectionChecks">
<li id="checkConnector" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Conector</strong><small>Iniciando…</small></span></li>
<li id="checkProject" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Projeto</strong><small>Verificando…</small></span></li>
<li id="checkSequence" data-state="waiting"><span class="check-dot" aria-hidden="true"></span><span><strong>Sequência ativa</strong><small>Verificando…</small></span></li>
</ul>
<p class="field-help">Este painel verifica só o Premiere. No seu assistente de IA, rode <strong>Verify Premiere connection</strong> para a checagem completa.</p>
</section>
<section class="card update-card" aria-live="polite">
<div class="card-head">
<h2>Atualizações <span class="card-kicker">MCP updates</span></h2>
</div>
<div class="update-body">
<div class="update-copy">
<strong id="updateTitle">Version 1.14.9</strong>
<span id="updateDetail">Verificando o servidor MCP global e a versão do conector…</span>
</div>
<button id="btnUpdate" class="button button-ghost" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
Verificar
</button>
</div>
</section>
<details class="advanced">
<summary>Pasta de troca de mensagens</summary>
<div class="advanced-body">
<label class="field-label" for="tempDir">Pasta local usada pela ponte</label>
<div class="path-field">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 5.5h5l1.5 2h8.5v8h-15v-10Z"/></svg>
<input type="text" id="tempDir" value="" spellcheck="false" autocomplete="off" aria-describedby="tempDirHelp">
</div>
<p class="field-help" id="tempDirHelp">Comandos e respostas são trocados por arquivos nesta pasta. Só mude se souber o que está fazendo.</p>
<button class="button button-ghost" id="btnSave" onclick="saveTempDir()" type="button">Salvar pasta</button>
</div>
</details>
<details class="log-details">
<summary>Detalhes técnicos <span class="log-hint">(atividade da ponte)</span></summary>
<div id="log" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log da ponte"></div>
</details>
</div>
<!-- =============================== MODELOS =============================== -->
<div class="tab-panel" id="tabModels" role="tabpanel" aria-labelledby="tabBtnModels" hidden>
<section class="card">
<div class="card-head"><h2>Idioma da transcrição</h2></div>
<label class="field-label" for="transcribeLanguage">Idioma falado nos vídeos</label>
<select id="transcribeLanguage" class="select-field" onchange="saveLanguage()"></select>
<p class="field-help">Salvo automaticamente e usado em toda transcrição nova.</p>
</section>
<section class="card">
<div class="card-head">
<h2>API Groq</h2>
<div class="hf-token-status" id="groqApiKeyStatus" data-state="unset">
<span class="check-dot" aria-hidden="true"></span>
<span id="groqApiKeyStatusText">Não configurada</span>
</div>
</div>
<label class="field-label" for="groqApiKey">API key</label>
<div class="path-field">
<input type="password" id="groqApiKey" placeholder="gsk_..." spellcheck="false" autocomplete="off">
<button type="button" class="path-field-toggle" onclick="toggleGroqApiKeyVisibility()" aria-label="Mostrar ou ocultar a chave Groq">👁</button>
</div>
<p class="field-help">Será usada para transcrição online quando o provider Groq estiver ativo.</p>
<button class="button button-primary" id="btnSaveGroqApiKey" onclick="saveGroqApiKey()" type="button">Salvar</button>
</section>
<section class="card">
<div class="card-head"><h2>Modelos Whisper</h2></div>
<p class="field-help">Modelos maiores acertam mais e demoram mais. O marcado como padrão é o pré-selecionado na aba Editar vídeo.</p>
<div class="model-list" id="modelList"></div>
</section>
<details class="advanced">
<summary>Pasta dos modelos</summary>
<div class="advanced-body">
<label class="field-label" for="modelsPath">Onde os modelos ficam salvos</label>
<div class="path-field">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M2.5 5.5h5l1.5 2h8.5v8h-15v-10Z"/></svg>
<input type="text" id="modelsPath" spellcheck="false" autocomplete="off" aria-describedby="modelsPathHelp">
</div>
<p class="field-help" id="modelsPathHelp">Mudar a pasta não move os modelos já baixados.</p>
<button class="button button-ghost" id="btnSaveModelsPath" onclick="saveModelsPath()" type="button">Salvar pasta</button>
</div>
</details>
<details class="log-details">
<summary>Detalhes técnicos <span class="log-hint">(downloads)</span></summary>
<div id="modelsLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log de download de modelos"></div>
</details>
</div>
<!-- =============================== AJUSTES =============================== -->
<div class="tab-panel" id="tabSettings" role="tabpanel" aria-labelledby="tabBtnSettings" hidden>
<section class="card">
<div class="card-head">
<h2>Token do Hugging Face</h2>
<div class="hf-token-status" id="hfTokenStatus" data-state="unset">
<span class="check-dot" aria-hidden="true"></span>
<span id="hfTokenStatusText">Não configurado</span>
</div>
</div>
<label class="field-label" for="hfToken">Access token</label>
<div class="path-field">
<input type="password" id="hfToken" placeholder="hf_..." spellcheck="false" autocomplete="off" aria-describedby="hfTokenHelp">
<button type="button" class="path-field-toggle" id="btnToggleHfToken" onclick="toggleHfTokenVisibility()" aria-label="Mostrar ou ocultar o token">👁</button>
</div>
<p class="field-help" id="hfTokenHelp">Autentica os downloads de modelos e libera a detecção de locutores.</p>
<div class="button-row">
<button class="button button-primary" id="btnSaveHfToken" onclick="saveHfToken()" type="button">Salvar</button>
<button class="button button-ghost" id="btnValidateHfToken" onclick="validateHfToken()" type="button">Validar</button>
</div>
</section>
<section class="card">
<div class="card-head">
<h2>Personalidades de edição</h2>
<button class="link-button" id="btnNewPersonality" onclick="startNewEditorPersonality()" type="button">+ Nova</button>
</div>
<p class="field-help">
Descreva em texto livre como a edição deve ser feita — ex.: “depoimento”: zoom suave nos momentos
emotivos, corta hesitação; “podcast”: ritmo ágil, corta silêncio agressivo. A personalidade ativa
viaja junto no JSON gerado na transcrição.
</p>
<div class="model-list" id="editorPersonalityList"></div>
<div id="editorPersonalityForm" class="inline-form" hidden>
<label class="field-label" for="editorPersonalityName">Nome</label>
<div class="path-field">
<input type="text" id="editorPersonalityName" placeholder="Ex: Depoimento emocional" spellcheck="false" autocomplete="off">
</div>
<label class="field-label" for="editorPersonalityText">Comportamento esperado</label>
<textarea id="editorPersonalityText" rows="6" placeholder="Descreva livremente como o editor deve tratar esse tipo de vídeo…"></textarea>
<div class="button-row">
<button id="btnSavePersonality" class="button button-primary" onclick="saveEditorPersonalityForm()" type="button">Salvar</button>
<button id="btnCancelPersonality" class="button button-ghost" onclick="cancelEditorPersonalityForm()" type="button">Cancelar</button>
</div>
</div>
</section>
</div>
</div>
<script src="CSInterface.js"></script>
<script src="updater.cjs"></script>
<script src="main.js"></script>
</body>
</html>