Tags: - Comma-separated input (tag1, tag2, tag3) with placeholder updated - Custom autocomplete dropdown on last token, sources from _voices DB + localStorage - All comma-separated tokens saved individually to localStorage reuse list Inspector opt-groups: - Each group wrapped in opt-group-body for clean collapse - Chevron (▾/▸) in title, click title to toggle open/closed - Transcript and Loudness open by default; others collapsed - Loudness title preserves dBFS meta span + chevron Voice list pane reorder: - vl-synth-panel moved above vl-filters - vl-filters now sits directly above voice-list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
240 lines
14 KiB
HTML
240 lines
14 KiB
HTML
<div class="section-head">
|
|
<span class="section-icon">🔊</span>
|
|
<div class="section-title">
|
|
<h2>My Voices</h2>
|
|
<p>Pick a voice on the left, edit on the right.</p>
|
|
</div>
|
|
<div class="section-head-actions">
|
|
<input type="search" class="section-search" id="section-voices-search" placeholder="Search voices…" autocomplete="off"
|
|
oninput="var f=document.getElementById('library-filter-text');if(f){f.value=this.value;f.dispatchEvent(new Event('input'));}">
|
|
<button class="btn-primary section-new-voice-btn" onclick="document.getElementById('add-new-voice-btn')?.click()">+ New voice</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workbench: list pane + inspector pane -->
|
|
<div class="voices-workbench" id="tab-library">
|
|
|
|
<!-- LEFT: compact voice list -->
|
|
<div class="voices-list-pane">
|
|
|
|
<div class="vl-toolbar">
|
|
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list">↻ Refresh</button>
|
|
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
|
|
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
|
|
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
|
|
<button class="btn-secondary vl-tb-btn" id="copy-active-voices-btn" title="Copy active voice names">Copy active</button>
|
|
</div>
|
|
|
|
<!-- TTS synth mode + preview sentence -->
|
|
<div class="vl-synth-panel">
|
|
<div class="vl-synth-mode-row">
|
|
<span class="vl-synth-icon">▶▶</span>
|
|
<span class="vl-synth-label">Synth uses</span>
|
|
<div class="vl-synth-seg" id="vl-synth-mode-seg">
|
|
<button class="vl-synth-seg-btn active" data-mode="preview"
|
|
title="Synthesize the preview sentence — good for comparing voices side by side">Preview text</button>
|
|
<button class="vl-synth-seg-btn" data-mode="transcript"
|
|
title="Synthesize this voice’s saved reference transcript — good for quality check vs. original recording">Reference transcript</button>
|
|
</div>
|
|
</div>
|
|
<div class="vl-preview-text-wrap" id="vl-preview-text-wrap">
|
|
<input type="text" class="vl-preview-input" id="vl-preview-sample"
|
|
placeholder="Sample sentence for TTS preview…"
|
|
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="vl-filters">
|
|
<input type="search" id="library-filter-text" placeholder="Search voices…" autocomplete="off" class="vl-search">
|
|
<select id="library-filter-lang" title="Language"><option value="">All</option></select>
|
|
<select id="library-filter-type" title="Type"><option value="">All types</option></select>
|
|
<label class="vl-disabled-label"><input type="checkbox" id="show-disabled-cb"> Disabled</label>
|
|
</div>
|
|
|
|
<div class="benchmark-confirm" id="benchmark-confirm" hidden role="group" aria-live="polite">
|
|
<strong id="benchmark-confirm-title">Benchmark active voices?</strong>
|
|
<span id="benchmark-confirm-text"></span>
|
|
<button class="btn-secondary" id="benchmark-confirm-cancel" type="button">Cancel</button>
|
|
<button class="benchmark-confirm-start" id="benchmark-confirm-start" type="button">Start benchmark</button>
|
|
</div>
|
|
|
|
<div id="voice-list"></div>
|
|
|
|
<div class="vl-footer">
|
|
<span id="voice-count" class="note"></span>
|
|
<button class="vl-add-btn" id="add-new-voice-btn" style="display:none" aria-hidden="true" tabindex="-1" title="Add a new voice to the library">+ Add voice</button>
|
|
</div>
|
|
|
|
</div><!-- /voices-list-pane -->
|
|
|
|
<!-- RIGHT: inspector / editor pane -->
|
|
<div class="voices-inspector-pane" id="voices-inspector">
|
|
<div class="inspector-placeholder">
|
|
<span>🔊</span>
|
|
<p>Pick a voice on the left<br>to edit it here</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /voices-workbench -->
|
|
|
|
<!-- Hidden scaffold — IDs required by app.js but not shown in new layout -->
|
|
<div id="vl-scaffold" style="display:none" aria-hidden="true">
|
|
|
|
<select id="library-filter-sex"><option value="">All</option><option value="F">F</option><option value="M">M</option><option value="N">N</option></select>
|
|
<select id="library-filter-rating"><option value="">Any</option><option value="5">5 stars</option><option value="4">4+ stars</option><option value="3">3+ stars</option><option value="1">Rated</option><option value="0">Unrated</option></select>
|
|
<button id="library-clear-filters" type="button">Clear filters</button>
|
|
<div id="library-filter-panel"></div>
|
|
<div class="library-insights" id="library-insights"></div>
|
|
<div class="disabled-info" id="disabled-info">
|
|
<strong>About disabled voices:</strong> The Active toggle moves the voice package between
|
|
<code>active_voices</code> and <code>hidden_voices</code>. Qwen3-TTS should scan only <code>active_voices</code>.
|
|
</div>
|
|
|
|
<!-- Audio playback bar -->
|
|
<div id="lib-audio-bar">
|
|
<div class="lib-audio-label" id="lib-audio-label">-</div>
|
|
<audio id="lib-audio" controls></audio>
|
|
</div>
|
|
|
|
<!-- Benchmark config panel -->
|
|
<div class="library-benchmark-panel">
|
|
<textarea id="benchmark-sample-text" placeholder="Sample sentence for benchmark"></textarea>
|
|
<select id="library-tts-backend-select"><option value="">Checking backends...</option></select>
|
|
<input type="number" id="library-target-db" value="-20" min="-60" max="-1" step="0.5">
|
|
<button id="normalize-volume-btn">Normalize volume</button>
|
|
<button id="benchmark-reset-sample-btn">Reset sample</button>
|
|
<button id="benchmark-use-preview-btn">Use preview text</button>
|
|
</div>
|
|
|
|
<!-- Benchmark progress -->
|
|
<div class="benchmark-progress" id="benchmark-progress" hidden aria-live="polite">
|
|
<div class="benchmark-progress-head">
|
|
<span id="benchmark-progress-label">Benchmarking voices...</span>
|
|
<span id="benchmark-progress-count">0 / 0</span>
|
|
</div>
|
|
<div class="benchmark-progress-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
|
|
<div id="benchmark-progress-bar"></div>
|
|
</div>
|
|
<div class="benchmark-live-stats" id="benchmark-live-stats">
|
|
<span>Elapsed -</span><span>Avg -</span><span>ETA -</span><span>OK 0</span><span>Slow 0</span><span>Errors 0</span>
|
|
</div>
|
|
<div class="benchmark-live-last" id="benchmark-live-last"></div>
|
|
</div>
|
|
|
|
<!-- Add new voice panel -->
|
|
<div class="lib-add-panel" id="lib-add-panel">
|
|
<div class="lib-add-stack">
|
|
<div class="lib-add-section">
|
|
<h2 style="margin-bottom:10px">1 Load or record source</h2>
|
|
<div class="lib-add-input-grid">
|
|
<div class="lib-add-import-box">
|
|
<input type="file" id="lib-add-file" accept="audio/*,video/*" style="display:none">
|
|
<div class="lib-add-drop" id="lib-add-drop">
|
|
<strong>Drop an audio / video file here</strong>
|
|
<span>WAV · MP3 · OGG · FLAC · M4A · MP4 · MKV · WEBM</span>
|
|
<span>or click to browse</span>
|
|
</div>
|
|
<div>
|
|
<label class="note" for="lib-add-url">Audio or video URL</label>
|
|
<div class="url-row">
|
|
<input type="text" id="lib-add-url" placeholder="Paste a direct MP3/WAV, YouTube, or Aiartes URL">
|
|
<button class="btn-primary" id="lib-add-url-btn">Download URL</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="lib-add-source-box lib-add-record-box">
|
|
<div class="record-head">
|
|
<span class="note">Record a fresh sample</span>
|
|
<div class="record-head-actions">
|
|
<button class="btn-secondary" id="lib-add-mic-help-btn">How to unlock mic</button>
|
|
</div>
|
|
</div>
|
|
<div class="lib-add-recorder-tools">
|
|
<div class="lib-add-mic-row">
|
|
<div class="mic-buttons">
|
|
<button class="btn-secondary" id="lib-add-monitor-btn">Check level</button>
|
|
<button class="btn-secondary" id="lib-add-monitor-stop" disabled>Stop monitor</button>
|
|
<button class="btn-red" id="lib-add-rec-start">● Record</button>
|
|
<button class="btn-secondary" id="lib-add-rec-stop" disabled>■ Stop</button>
|
|
<span class="mic-timer" id="lib-add-rec-time">0:00</span>
|
|
</div>
|
|
<div class="mic-monitor-box">
|
|
<div class="mic-monitor-head">
|
|
<span>Input level</span>
|
|
<span class="meter-readout" id="lib-add-db-readout">-∞ dB</span>
|
|
</div>
|
|
<div class="mic-meter" id="lib-add-mic-meter" aria-hidden="true"></div>
|
|
<div class="mic-gain-row">
|
|
<label for="lib-add-mic-gain">Mic gain</label>
|
|
<input id="lib-add-mic-gain" type="range" min="0" max="2" step="0.05" value="1">
|
|
<span class="mic-gain-value" id="lib-add-mic-gain-value">1.00x</span>
|
|
</div>
|
|
<div class="note" style="margin-top:5px">Best peaks: -18 to -9 dB, never red.</div>
|
|
</div>
|
|
</div>
|
|
<div class="sample-read-box">
|
|
<div class="sample-head">
|
|
<label for="lib-add-sample-lang">Read sample</label>
|
|
<select id="lib-add-sample-lang">
|
|
<option value="EN">English</option><option value="DE">Deutsch</option>
|
|
<option value="IT">Italiano</option><option value="ES">Español</option>
|
|
<option value="FR">Français</option><option value="PT">Português</option>
|
|
<option value="NL">Nederlands</option><option value="PL">Polski</option>
|
|
</select>
|
|
</div>
|
|
<textarea class="sample-sentence" id="lib-add-sample-text" spellcheck="true"></textarea>
|
|
<div class="sample-actions">
|
|
<button class="btn-secondary" id="lib-add-use-sample">Use as transcript</button>
|
|
<button class="btn-secondary" id="lib-add-reset-sample">Reset sentence</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mic-help-panel" id="lib-add-mic-help">
|
|
<strong>If the microphone is blocked:</strong>
|
|
<ul>
|
|
<li><strong>Chrome/Brave/Edge:</strong> click the lock icon in the address bar → Microphone → Allow, then reload.</li>
|
|
<li><strong>Firefox:</strong> click the lock icon → remove Blocked → Allow, then reload.</li>
|
|
<li><strong>Safari:</strong> Settings → Websites → Microphone → allow this site.</li>
|
|
</ul>
|
|
</div>
|
|
<div class="opt-status" id="lib-add-status">Load a file, paste a URL, or record. Then trim, name, and save.</div>
|
|
</div>
|
|
</div>
|
|
<div class="lib-add-audio-row">
|
|
<audio id="lib-add-audio" controls style="display:none"></audio>
|
|
<div class="btn-row">
|
|
<button class="btn-secondary" id="lib-add-auto-trim">Auto trim</button>
|
|
<button class="btn-primary" id="lib-add-save-crop">Crop selection</button>
|
|
<button class="btn-secondary" id="lib-add-play">Play crop</button>
|
|
</div>
|
|
</div>
|
|
<canvas class="opt-wave" id="lib-add-wave" style="display:none;margin-top:10px"></canvas>
|
|
<div class="opt-controls">
|
|
<div class="opt-field"><label>Start</label><input id="lib-add-start" type="number" step="0.01" value="0"></div>
|
|
<div class="opt-field"><label>End</label><input id="lib-add-end" type="number" step="0.01" value="0"></div>
|
|
<button class="btn-primary" id="lib-add-save-crop-bottom">Crop selection</button>
|
|
<span class="crop-duration-hint" id="lib-add-crop-hint">Select 3-20 seconds for best cloning.</span>
|
|
</div>
|
|
</div>
|
|
<div class="lib-add-section">
|
|
<h2 style="margin-bottom:10px">2 Name and save voice</h2>
|
|
<div class="lib-add-name-grid">
|
|
<div class="opt-field"><label>Language</label><select id="lib-add-lang">
|
|
<option value="EN">EN</option><option value="DE">DE</option><option value="IT">IT</option>
|
|
<option value="ES">ES</option><option value="FR">FR</option><option value="PT">PT</option>
|
|
<option value="NL">NL</option><option value="PL">PL</option>
|
|
</select></div>
|
|
<div class="opt-field"><label>Gender</label><input id="lib-add-gender" value="N"></div>
|
|
<div class="opt-field"><label>Voice ID</label><input id="lib-add-voice-id" placeholder="EN_N_NewVoice"></div>
|
|
</div>
|
|
<textarea id="lib-add-transcript" placeholder="Reference transcript" style="margin-top:10px"></textarea>
|
|
<div class="btn-row" style="margin-top:10px">
|
|
<button class="btn-secondary" id="lib-add-recognize">Recognise text</button>
|
|
<button class="btn-green" id="lib-add-save">Save voice</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /vl-scaffold -->
|