tts-voice-creator-clone-and.../static/sections/s-voices.html
mARTin-B78 a325f53e62 feat: Implement sortable Table View and Inspector drag-and-drop support
- Added a new 'Table View' button to the library sort bar.
- Implemented a CSS grid layout to display voice properties in a high-density table.
- Added sortable column headers (Name, Lang, Gender, Speed, dBFS, Length, Rating, Source, Seed, Note, Tags, Active).
- Aligned CSS grid to account for the bulk edit checkbox injection.
- Added drag-and-drop profile image support to the Inspector's large avatar.
- Ensured picture updates instantly synchronize across the List, Table, and Inspector views.
2026-06-22 14:40:39 +02:00

310 lines
20 KiB
HTML

<div class="section-head">
<span class="section-icon"><span class="mdi mdi-account-voice"></span></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&#x2026;" 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 — two cards -->
<div class="voices-list-pane">
<!-- Card 1: TTS synth mode + preview sentence -->
<div class="vl-card vl-card-synth">
<div class="vl-synth-panel">
<div class="vl-synth-mode-row">
<span class="vl-synth-icon"><span class="mdi mdi-play"></span><span class="mdi mdi-play"></span></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&#x2019;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">
<div class="vl-preview-row">
<button class="vl-preview-lang-btn" id="vl-preview-lang-btn"
title="English — click to change sample language"
onclick="openPreviewLangPicker(this)" data-lang="EN">
<span class="fi fi-gb"></span>
</button>
<textarea class="vl-preview-input" id="vl-preview-sample" rows="2"
placeholder="Sample sentence for TTS preview&#x2026;"
oninput="var t=document.getElementById('benchmark-sample-text');if(t){t.value=this.value;t.dispatchEvent(new Event('input'));}"
></textarea>
</div>
</div>
</div>
</div><!-- /vl-card-synth -->
<!-- Card 2: filters + sort + voice list + toolbar -->
<div class="vl-card vl-card-list">
<div class="vl-filters">
<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="vl-sort-bar">
<input type="search" id="library-filter-text" placeholder="&#x1F50D; Search voices&#x2026;" autocomplete="off" class="vl-search vl-search-main">
<span class="vl-sort-label">Sort</span>
<select id="voice-sort-field" title="Sort field">
<option value="id">Name</option>
<option value="flag">Language</option>
<option value="gender">Gender</option>
<option value="benchmark">Speed</option>
<option value="rating">Rating</option>
<option value="duration">Duration</option>
<option value="dbfs">Volume dB</option>
<option value="source">Source</option>
<option value="seed">Seed</option>
<option value="note">Note</option>
<option value="tag">Tags</option>
<option value="enabled">Active</option>
</select>
<button id="voice-sort-dir" class="vl-sort-dir-btn" title="Toggle sort direction"><span class="mdi mdi-arrow-up"></span></button>
<button id="voice-group-tag-btn" class="vl-sort-dir-btn" title="Group into virtual folders by tag"><span class="mdi mdi-folder-outline"></span></button>
<button id="voice-table-view-btn" class="vl-sort-dir-btn" title="Toggle full-width table view"><span class="mdi mdi-table"></span></button>
<button id="vl-select-all-visible" class="vl-sort-dir-btn" title="Select all visible voices (toggle)"><span class="mdi mdi-checkbox-multiple-marked-outline"></span></button>
</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-groups-bar" class="voice-groups-bar" hidden></div>
<!-- Bulk-edit toolbar — appears when voices are selected -->
<div class="vl-bulk-bar" id="vl-bulk-bar" hidden>
<span class="vl-bulk-count" id="vl-bulk-count">0 selected</span>
<button class="vl-bulk-btn" id="vl-bulk-select-all" title="Select all visible"><span class="mdi mdi-checkbox-multiple-marked-outline"></span> All</button>
<button class="vl-bulk-btn" id="vl-bulk-deselect" title="Clear selection"><span class="mdi mdi-checkbox-multiple-blank-outline"></span> None</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn" id="vl-bulk-tag" title="Add / replace tag on selected voices"><span class="mdi mdi-tag-outline"></span> Set tag</button>
<button class="vl-bulk-btn" id="vl-bulk-hide" title="Disable (hide) selected voices"><span class="mdi mdi-eye-off-outline"></span> Hide</button>
<button class="vl-bulk-btn" id="vl-bulk-unhide" title="Enable (unhide) selected voices"><span class="mdi mdi-eye-outline"></span> Unhide</button>
<button class="vl-bulk-btn" id="vl-bulk-rating" title="Set star rating on selected"><span class="mdi mdi-star-outline"></span> Rate</button>
<div class="vl-bulk-sep"></div>
<button class="vl-bulk-btn vl-bulk-danger" id="vl-bulk-delete" title="Delete selected voices permanently"><span class="mdi mdi-delete-outline"></span> Delete</button>
</div>
<div class="vl-table-header" id="vl-table-header">
<div></div>
<div>Img</div>
<div>Play</div>
<div class="vl-th-sortable" data-sort="id">Name <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="flag">Lang <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="gender">Gender <span class="mdi mdi-sort"></span></div>
<div>Type</div>
<div class="vl-th-sortable" data-sort="benchmark">Speed <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="dbfs">dBFS <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="duration">Length <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="rating">Rating <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="source">Source <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="seed">Seed <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="note">Note <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="tag">Tags <span class="mdi mdi-sort"></span></div>
<div class="vl-th-sortable" data-sort="enabled">Active <span class="mdi mdi-sort"></span></div>
</div>
<div id="voice-list" tabindex="0" role="region" aria-label="Voice library"></div>
<div class="vl-toolbar">
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list"><span class="mdi mdi-refresh"></span> 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>
<button class="btn-secondary vl-tb-btn" id="precompute-embeddings-btn" title="Warm all active voices so the TTS engine pre-computes &amp; caches each speaker embedding (.pt) — makes first playback instant"><span class="mdi mdi-flash-outline"></span> Precompute</button>
<button class="btn-secondary vl-tb-btn" id="seed-batch-all-btn" title="Generate &amp; cache Seed Finder samples for every active voice (skips already-cached; resumable)"><span class="mdi mdi-dice-multiple-outline"></span> Batch seeds</button>
</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><!-- /vl-card-list -->
</div><!-- /voices-list-pane -->
<!-- RIGHT: inspector / editor pane -->
<div class="voices-inspector-pane" id="voices-inspector">
<div class="inspector-placeholder">
<span><span class="mdi mdi-account-voice"></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 &middot; MP3 &middot; OGG &middot; FLAC &middot; M4A &middot; MP4 &middot; MKV &middot; 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"><span class="mdi mdi-record-circle-outline"></span> Record</button>
<button class="btn-secondary" id="lib-add-rec-stop" disabled><span class="mdi mdi-stop-circle-outline"></span> 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">-<span class="mdi mdi-infinity"></span> 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&ntilde;ol</option>
<option value="FR">Fran&ccedil;ais</option><option value="PT">Portugu&ecirc;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 &rarr; Microphone &rarr; Allow, then reload.</li>
<li><strong>Firefox:</strong> click the lock icon &rarr; remove Blocked &rarr; Allow, then reload.</li>
<li><strong>Safari:</strong> Settings &rarr; Websites &rarr; Microphone &rarr; 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 -->