Loader: - Post-init modules (engines, ai-backends, generation, conversation) now load AFTER the skeleton is removed instead of before. The UI is visible ~500 ms sooner on average; those four modules load while the user is already browsing Voices / Clone / Design. - Removed the sttReady event approach that was triggering a duplicate /api/stt-backends call; init.js already populates all STT selects once on startup. Skeleton: - Replaced the card-grid placeholder with a two-column workbench skeleton (voice list rows on the left + inspector placeholder on the right) that matches the real My Voices layout. Connect Apps: - MCP Server, /speak REST endpoint, and Global hotkey daemon sections moved from Settings → About to Connect Apps, where they belong. - About page now has GitHub + Releases links instead. Clone section: - Added a hint note beneath the sample-text textarea reminding the user to replace the placeholder name (Sam / Alex / Marco …) with their own. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
156 lines
9.1 KiB
HTML
156 lines
9.1 KiB
HTML
<div class="section-head">
|
||
<span class="section-icon"><span class="mdi mdi-microphone-variant"></span></span>
|
||
<div class="section-title">
|
||
<h2>Clone a Voice</h2>
|
||
<p>Upload or record 3-20 seconds of audio, trim it, then save it as a reusable voice clone.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Step 1: Load audio -->
|
||
<div class="tab-content" id="tab-source">
|
||
<div class="card">
|
||
<h2>Step 1 — Load audio</h2>
|
||
<p class="card-subtitle">Drop a file, paste a URL, or record directly from your microphone.</p>
|
||
<div id="drop-zone">
|
||
<strong>Drop an audio / video file here</strong>
|
||
<span>WAV · MP3 · OGG · FLAC · M4A · MP4 · MKV · WEBM</span>
|
||
<span style="margin-top:10px;font-size:12px;color:var(--subtext)">or click to browse</span>
|
||
<input type="file" id="file-input" accept=".wav,.mp3,.ogg,.opus,.flac,.m4a,.mp4,.mkv,.webm,.mov,.avi,audio/*,video/*">
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>YouTube / URL</h2>
|
||
<p class="card-subtitle">Download audio from a direct link or YouTube video.</p>
|
||
<div class="url-row">
|
||
<input type="text" id="yt-url" placeholder="https://www.youtube.com/watch?v=...">
|
||
<button class="btn-primary" id="yt-btn">Download</button>
|
||
</div>
|
||
<div id="yt-progress"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Microphone</h2>
|
||
<p class="card-subtitle">Record a fresh sample with live input level monitoring.</p>
|
||
<div class="sample-read-box" style="margin-bottom:12px">
|
||
<div class="sample-head">
|
||
<label for="clone-sample-lang">Read aloud</label>
|
||
<select id="clone-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="clone-sample-text" spellcheck="true">Hello! My name is Sam, and this is my voice. I can speak softly or with great strength. The crisp winter air, warm firelight, and the gentle sound of rain — these are the things I love. Can you hear how clearly I speak?</textarea>
|
||
<p class="note" style="margin-top:6px"><span class="mdi mdi-pencil-outline"></span> Replace <strong>the name</strong> (Sam / Alex / Marco …) with your own name before recording.</p>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-secondary" id="clone-monitor-btn"><span class="mdi mdi-waveform"></span> Check level</button>
|
||
<button class="btn-secondary" id="clone-monitor-stop" disabled>Stop monitor</button>
|
||
<button class="btn-red" id="rec-start-btn"><span class="mdi mdi-record-circle-outline"></span> Record</button>
|
||
<button class="btn-secondary" id="rec-stop-btn" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
|
||
<div id="rec-indicator"><div id="rec-dot"></div><span id="rec-time">0:00</span></div>
|
||
</div>
|
||
<div class="mic-monitor-box" id="clone-monitor-box" style="margin-top:10px">
|
||
<div class="mic-monitor-head">
|
||
<span>Input level</span>
|
||
<span class="meter-readout" id="clone-db-readout">-∞ dB</span>
|
||
</div>
|
||
<div class="mic-meter" id="clone-mic-meter" aria-hidden="true"></div>
|
||
<canvas id="clone-live-wave" class="mic-live-wave" width="300" height="48" aria-hidden="true"></canvas>
|
||
<div class="mic-gain-row">
|
||
<label for="clone-mic-gain">Mic gain</label>
|
||
<input id="clone-mic-gain" type="range" min="0" max="2" step="0.05" value="1">
|
||
<span class="mic-gain-value" id="clone-mic-gain-value">1.00x</span>
|
||
</div>
|
||
<div class="note" style="margin-top:4px">Best peaks: −18 to −9 dB. Never red.</div>
|
||
</div>
|
||
</div>
|
||
<div class="card" id="waveform-card" style="display:none">
|
||
<h2>Step 2 — Trim selection</h2>
|
||
<p class="card-subtitle">Select the best 3–20 s of clean speech from the waveform.</p>
|
||
<div id="waveform-wrap"><div id="waveform"></div></div>
|
||
<div class="time-row">
|
||
<label>Start (s) <input type="number" id="trim-start" value="0" min="0" step="0.1"></label>
|
||
<label>End (s) <input type="number" id="trim-end" value="0" min="0" step="0.1"></label>
|
||
<span id="trim-duration" class="dur-ok">0.0 s</span>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-primary" id="trim-btn">Confirm trim → Name & Save</button>
|
||
<button class="btn-secondary" id="auto-trim-btn">Auto trim</button>
|
||
<button class="btn-secondary" id="play-btn"><span class="mdi mdi-play"></span> Play</button>
|
||
<button class="btn-secondary" id="play-selection-btn"><span class="mdi mdi-play"></span> Play selection</button>
|
||
</div>
|
||
</div>
|
||
</div><!-- /tab-source -->
|
||
|
||
<!-- Step 3: Name and save -->
|
||
<div class="tab-content" id="tab-save">
|
||
<div class="card">
|
||
<h2>Step 3 — Name the voice</h2>
|
||
<p class="card-subtitle">Assign a unique ID, language, and gender — e.g. <code>EN_F_Anna</code>.</p>
|
||
<div class="voice-id-row">
|
||
<input type="text" id="voice-id-input" placeholder="e.g. EN_F_Anna or MyCustomVoice" spellcheck="false">
|
||
</div>
|
||
<div id="voice-id-hint" class="note" style="min-height:1.2em"></div>
|
||
<details class="helper">
|
||
<summary>Build from parts (LANG · GENDER · Name helper)</summary>
|
||
<div class="helper-body">
|
||
<div class="field">
|
||
<label>Language</label>
|
||
<select id="lang-select">
|
||
<option>EN</option><option>DE</option><option>ZH</option><option>FR</option>
|
||
<option>ES</option><option>JA</option><option>KO</option><option>IT</option>
|
||
<option>PT</option><option>RU</option><option>AR</option><option>PL</option>
|
||
<option>NL</option><option>SV</option><option>TR</option><option>HI</option>
|
||
</select>
|
||
</div>
|
||
<div class="field">
|
||
<label>Gender</label>
|
||
<select id="gender-select">
|
||
<option value="F">F — Female</option>
|
||
<option value="M">M — Male</option>
|
||
<option value="N">N — Neutral</option>
|
||
</select>
|
||
</div>
|
||
<div class="field" style="flex:1;min-width:120px">
|
||
<label>Name (no spaces)</label>
|
||
<input type="text" id="name-input" placeholder="Anna">
|
||
</div>
|
||
<button class="btn-secondary" id="helper-apply-btn" style="align-self:flex-end">Apply →</button>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Transcript (reference text)</h2>
|
||
<p class="card-subtitle">The spoken text that matches this audio — type it or auto-transcribe.</p>
|
||
<div class="btn-row" style="gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:8px">
|
||
<label style="font-size:13px;color:var(--subtext);white-space:nowrap">Recognition engine:</label>
|
||
<select id="clone-stt-backend" style="flex:1;min-width:160px"><option value="configured">Configured Whisper/STT</option></select>
|
||
<button class="btn-secondary" id="clone-refresh-stt-btn" type="button">Refresh</button>
|
||
</div>
|
||
<div class="btn-row">
|
||
<button class="btn-secondary" id="transcribe-btn"><span class="mdi mdi-message-text-outline"></span> Auto-transcribe</button>
|
||
<span id="transcribe-status" style="font-size:13px;color:var(--subtext)"></span>
|
||
</div>
|
||
<textarea id="transcript-area" placeholder="Type or auto-transcribe the spoken text…"></textarea>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Audio preview</h2>
|
||
<p class="card-subtitle">Play back the trimmed audio before saving to the library.</p>
|
||
<audio id="trim-audio" controls style="display:none"></audio>
|
||
<p id="no-audio-hint" class="note">No audio loaded yet. Load a file above (Step 1) or use Voice Design below.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Step 4 — Save to library</h2>
|
||
<p class="card-subtitle">Write the cloned voice to your active voices folder.</p>
|
||
<div class="btn-row">
|
||
<button class="btn-green" id="save-btn"><span class="mdi mdi-memory"></span> Save to Voice Library</button>
|
||
</div>
|
||
<div id="save-result" class="note" style="display:none"></div>
|
||
</div>
|
||
</div><!-- /tab-save -->
|