Files
jhonny-editor/code/cep-plugin/index.html
T
João Henrique 0a01b16148 feat: criado repositório jhonny-editor no Gitea
criado repositório jhonny-editor no Gitea
adicionado script admin/deploy.command com commit automático
atualizado admin/DEV-NOTES.md com template limpo

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

 3 files changed, 83 insertions(+), 21 deletions(-)

Arquivos:
  - code/cep-plugin/index.html
  - code/cep-plugin/main.js
  - code/cep-plugin/styles.css
2026-09-08 17:30:08 -04:00

525 lines
31 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="tabBtnScanner" role="tab" aria-selected="false" aria-controls="tabScanner" onclick="switchTab('scanner')" type="button">
<span class="tab-icon" aria-hidden="true">◉</span>Scanner
</button>
<button class="tab-button" id="tabBtnRetakes" role="tab" aria-selected="false" aria-controls="tabRetakes" onclick="switchTab('retakes')" type="button">
<span class="tab-icon" aria-hidden="true">↻</span>Retakes
</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>
<!-- O painel é reposicionado para a aba que iniciou a operação. -->
<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>
<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 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>
<pre class="task-error-details" id="taskErrorDetails" hidden></pre>
</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>
<!-- ============================== SCANNER =============================== -->
<div class="tab-panel" id="tabScanner" role="tabpanel" aria-labelledby="tabBtnScanner" hidden>
<p class="tab-intro">Escolha o que será lido no arquivo original. O Scanner usa os tempos da timeline, mas não exporta frames pelo Premiere.</p>
<section class="card">
<div class="card-head"><h2>Amostragem e faixas</h2></div>
<label class="field-label" for="scannerInterval">Segundos por quadro</label>
<div class="range-field-row">
<input id="scannerInterval" type="range" min="0.2" max="10" step="0.1" value="1" oninput="scannerUpdateIntervalLabel()">
<span class="range-field-value" id="scannerIntervalValue">1 s/quadro</span>
</div>
<p class="field-help">Quanto menor, mais frames e mais detalhado (e mais lento). Valores maiores aceleram a análise, principalmente em clipes curtos.</p>
<label class="field-label" for="scannerResolution">Resolução da análise</label>
<select id="scannerResolution" class="select-field">
<option value="0">Original — sem redimensionar</option>
<option value="1080" selected>1080p — recomendado</option>
<option value="720">720p — mais rápido</option>
</select>
<p class="field-help">Reduzir a resolução acelera a extração e a leitura Vision sem perder precisão no rosto, pose ou objetos.</p>
<button class="button button-ghost" id="btnScannerDetectTracks" onclick="scannerDetectTracks()" type="button">Detectar faixas da sequência</button>
<p class="field-help" id="scannerTracksHelp">Abra a sequência desejada e detecte as faixas para selecioná-las.</p>
<div class="scanner-track-grid"><div><span class="field-label">Faixas de vídeo</span><div class="scanner-track-list" id="scannerVideoTracks"></div></div><div><span class="field-label">Faixas de áudio</span><div class="scanner-track-list" id="scannerAudioTracks"></div></div></div>
</section>
<section class="card"><div class="card-head"><h2>Transcrição</h2><span class="card-kicker" id="scannerModelCount">—</span></div>
<div class="field-grid"><div><label class="field-label" for="scannerTranscribeModel">Modelo instalado</label><select id="scannerTranscribeModel" class="select-field"></select></div><div><label class="field-label" for="scannerTranscribeLanguage">Idioma</label><select id="scannerTranscribeLanguage" class="select-field"></select></div></div>
<label class="checkbox-field"><input id="scannerDiarization" type="checkbox"><span>Identificar locutores após transcrever</span></label>
<div class="hf-token-status" id="scannerHfStatus" data-state="unset"><span class="check-dot" aria-hidden="true"></span><span id="scannerHfStatusText">Hugging Face não configurado</span><button class="link-button" type="button" onclick="switchTab('settings')">Configurar</button></div>
<p class="field-help">A lista inclui somente modelos encontrados nas pastas locais configuradas. O token do Hugging Face é usado apenas na diarização; nunca é salvo no perfil nem no relatório.</p>
</section>
<div class="scanner-options-grid"><section class="card"><div class="card-head"><h2>O que detectar</h2></div>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="faces" checked><span>Rostos e qualidade facial</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="pessoas" checked><span>Pessoas</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="pose" checked><span>Pose e mãos</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="ocr" checked><span>Texto e códigos</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="categorias" checked><span>Objetos e categorias</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="estetica" checked><span>Estética, horizonte e geometria</span></label>
<label class="checkbox-field"><input type="checkbox" data-scanner-resource="segmentacao_de_pessoas" checked><span>Segmentação de pessoas</span></label>
</section>
<section class="card"><div class="card-head"><h2>Leitura temporal</h2></div>
<label class="checkbox-field"><input id="scannerScenes" type="checkbox" checked><span>Agrupar cenas</span></label>
<label class="checkbox-field"><input id="scannerContinuity" type="checkbox" checked><span>Medir continuidade e cortes</span></label>
<label class="checkbox-field"><input id="scannerReframe" type="checkbox"><span>Guardar geometria para reenquadramento</span></label>
<label class="checkbox-field"><input id="scannerInterpret" type="checkbox" checked><span>Descrever a cena com Foundation Models</span></label>
<div class="button-row"><button class="button button-ghost" onclick="saveScannerProfile()" type="button">Salvar perfil</button><button class="button button-primary" id="btnScannerProcess" onclick="scannerProcess()" type="button" disabled>Processar</button></div>
<p class="field-help" id="scannerProfileStatus">Perfil padrão pronto.</p>
<div class="callout callout-info" id="scannerReportCard" hidden><strong>Relatório pronto</strong><p id="scannerReportPath"></p><div class="button-row"><button class="button button-ghost" onclick="scannerOpenReport()" type="button">Abrir JSON</button><button class="button button-ghost" onclick="scannerCopyReport()" type="button">Copiar conteúdo</button></div></div>
</section></div>
</div>
<!-- ============================== RETAKES ============================== -->
<div class="tab-panel" id="tabRetakes" role="tabpanel" aria-labelledby="tabBtnRetakes" hidden>
<p class="tab-intro">Identifica repetições e tentativas alternativas usando a transcrição do Scanner. O painel nunca corta o vídeo: você revisa e decide.</p>
<!-- 1. Arquivo ou conjunto analisado -->
<section class="card">
<div class="card-head"><h2>Arquivo analisado</h2><span class="card-kicker" id="retakesStatusGeral">—</span></div>
<div class="field-grid">
<div><label class="field-label" for="retakesCaso">Caso do Scanner</label>
<select id="retakesCaso" class="select-field" onchange="retakesCarregarCaso()"></select></div>
<div><button class="button button-ghost" onclick="retakesListarCasos()" type="button">Atualizar lista</button></div>
</div>
<p class="field-help" id="retakesInfoCaso">Selecione um caso já processado pelo Scanner.</p>
</section>
<!-- 2/3. Configurações e critérios -->
<div class="scanner-options-grid">
<section class="card"><div class="card-head"><h2>Critérios</h2></div>
<label class="field-label" for="retakesCriterio">Critério de confirmação</label>
<select id="retakesCriterio" class="select-field">
<option value="conservador">Conservador — só evidências fortes</option>
<option value="equilibrado" selected>Equilibrado — prováveis e fortes</option>
<option value="sensivel">Sensível — também possíveis</option>
</select>
<label class="checkbox-field"><input id="retakesReaproveitar" type="checkbox" checked><span>Reaproveitar análise existente</span></label>
<p class="field-help">A comparação textual, os reinícios de fala e as pausas já fazem parte da análise da engine.</p>
</section>
<section class="card"><div class="card-head"><h2>Análise visual auxiliar</h2></div>
<label class="checkbox-field"><input id="retakesUsarVisual" type="checkbox"><span>Usar cortes visuais como evidência</span></label>
<p class="field-help">Reforça a confiança, mas nunca decide sozinha. Reutiliza as cenas já analisadas.</p>
<div class="button-row">
<button class="button button-primary" id="btnRetakesAnalisar" onclick="retakesAnalisar()" type="button">Analisar retakes</button>
</div>
</section>
</div>
<!-- 4. Progresso -->
<div class="callout callout-info" id="retakesProgressoCard" hidden>
<strong id="retakesProgressoEtapa">Analisando retakes…</strong>
<p id="retakesProgressoDetalhe">—</p>
</div>
<!-- 5/6. Resultados e revisão -->
<section class="card">
<div class="card-head"><h2>Resultados</h2><span class="card-kicker" id="retakesContagem">—</span></div>
<label class="field-label" for="retakesFiltro">Filtrar</label>
<select id="retakesFiltro" class="select-field" onchange="retakesRenderResultados()">
<option value="todos" selected>Todos</option>
<option value="fortes">Fortes</option>
<option value="possiveis">Possíveis e prováveis</option>
<option value="pendente_de_revisao">Pendentes de revisão</option>
<option value="confirmado">Confirmados</option>
<option value="nao_e_retake">Rejeitados</option>
<option value="ignorado">Ignorados</option>
</select>
<div id="retakesResultados" role="list" aria-label="Grupos de retakes"></div>
<p class="field-help" id="retakesSemResultados" hidden>Nenhum retake identificado com os critérios atuais.</p>
</section>
<div id="retakesLog" role="log" aria-live="polite" aria-relevant="additions" tabindex="0" aria-label="Log de retakes"></div>
</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>