tts-voice-creator-clone-and.../static/index.html

282 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- viewport-fit=cover respects the iPhone notch/safe areas; allow zoom for accessibility -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="format-detection" content="telephone=no">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#2563EB">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/static/icon.svg">
<title>TTS Voice Creator</title>
<script>
/* PWA: register the network-first service worker (offline shell + installable) */
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(() => {}));
}
</script>
<!-- ── Preconnect to CDN (flag SVGs loaded lazily by JS) ──────────────── -->
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin>
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
<link rel="stylesheet" href="/static/style.css?v=1.8.0-6">
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
<link rel="preload" as="style"
href="https://cdn.jsdelivr.net/npm/flag-icons@7.3.2/css/flag-icons.min.css"
onload="this.onload=null;this.rel='stylesheet'">
<noscript>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/flag-icons@7.3.2/css/flag-icons.min.css">
</noscript>
<!-- ── Skeleton animation (inline — zero extra request) ──────────────── -->
<style>
@keyframes sk-shimmer {
0% { background-position: -400% 0; }
100% { background-position: 400% 0; }
}
.sk {
background: linear-gradient(
90deg,
var(--border, #e5e7eb) 25%,
var(--panel, #f3f4f6) 50%,
var(--border, #e5e7eb) 75%
);
background-size: 400% 100%;
animation: sk-shimmer 1.6s ease-in-out infinite;
border-radius: 6px;
}
#app-skeleton {
padding: 28px 32px;
display: flex;
flex-direction: column;
gap: 20px;
opacity: 1;
transition: opacity .25s ease;
}
#app-skeleton.sk-hidden { opacity: 0; pointer-events: none; }
.sk-head { display: flex; align-items: center; gap: 12px; }
.sk-icon { width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0; }
.sk-title { display: flex; flex-direction: column; gap: 7px; }
.sk-toolbar { display: flex; gap: 10px; align-items: center; }
.sk-row { display: flex; gap: 10px; align-items: center; }
.sk-workbench { display: grid; grid-template-columns: 300px 1fr; gap: 0; border: 1px solid var(--border,#e5e7eb); border-radius: 10px; overflow: hidden; min-height: 480px; }
.sk-list { border-right: 1px solid var(--border,#e5e7eb); display: flex; flex-direction: column; }
.sk-list-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--border,#e5e7eb); }
.sk-avatar { width: 36px; height: 36px; border-radius: 8px; flex-shrink: 0; }
.sk-list-body { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 0; }
.sk-insp { padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.sk-insp-head { display: flex; flex-direction: column; gap: 8px; }
@media (max-width: 700px) { .sk-workbench { grid-template-columns: 1fr; } .sk-insp { display: none; } }
</style>
</head>
<body>
<a class="skip-link" href="#main-content">Skip to main content</a>
<!-- Hidden tabs nav — required for JS backend availability detection -->
<nav class="tabs" style="display:none" aria-hidden="true">
<div class="tab active" data-tab="library" data-backend-required="voice_clone" tabindex="-1"><span class="tab-title">Voice Clone</span></div>
<div class="tab" data-tab="design" data-backend-required="voice_design" tabindex="-1"><span class="tab-title">Voice Design</span></div>
<div class="tab" data-tab="custom" data-backend-required="customvoice" tabindex="-1"><span class="tab-title">Voice Custom</span></div>
<div class="tab" data-tab="generation" data-backend-required="any_tts" tabindex="-1"><span class="tab-title">TTS Generation</span></div>
<div class="tab" data-tab="stt-tts" data-backend-required="any_tts" tabindex="-1"><span class="tab-title">STT-TTS</span></div>
<div class="tab" data-tab="performance" data-backend-required="any_tts" tabindex="-1"><span class="tab-title">Performance</span></div>
<div class="tab" data-tab="routing" tabindex="-1"><span class="tab-title">Routing</span></div>
<div class="tab" data-tab="integrations" tabindex="-1"><span class="tab-title">How to</span></div>
<div class="tab" data-tab="getvoices" tabindex="-1"><span class="tab-title">Get Voices</span></div>
<div class="tab" data-tab="settings" tabindex="-1"><span class="tab-title">Settings</span></div>
</nav>
<!-- Mobile top bar (hidden on desktop via CSS) -->
<div id="mobile-topbar">
<button id="sidebar-toggle" aria-label="Open navigation"><span class="mdi mdi-menu"></span></button>
<span class="mobile-brand"><span class="mdi mdi-microphone-variant"></span> Voice Creator</span>
</div>
<!-- Backdrop for sidebar drawer on mobile -->
<div id="sidebar-backdrop"></div>
<!-- App shell -->
<div id="app-shell">
<!-- Sidebar (renders immediately — no JS needed) -->
<aside id="sidebar" tabindex="0" aria-label="Navigation">
<div class="sidebar-brand">
<h1><span class="mdi mdi-microphone-variant"></span> Voice Creator</h1>
<p>Clone &middot; Design &middot; Deploy</p>
</div>
<nav class="sidebar-nav">
<div class="nav-group-label">Voices</div>
<div class="nav-item nav-tree-head active" data-nav-section="s-voices" id="nav-voices-head" onclick="navTo('s-voices')">
<span class="nav-icon"><span class="mdi mdi-account-voice"></span></span>
<span class="nav-label">My Voices</span>
<span class="nav-badge" id="nav-voices-count"></span>
<span class="nav-chevron" id="nav-voices-chevron" onclick="event.stopPropagation();toggleNavTree('nav-voices-tree','nav-voices-chevron')"><span class="mdi mdi-chevron-down"></span></span>
</div>
<div class="nav-tree open" id="nav-voices-tree">
<div class="nav-tree-item is-active" data-voice-cat="all" onclick="navVoicesCat('all')"><span class="mdi mdi-account-voice"></span> All voices <span class="ntc" id="ntc-all"></span></div>
<div class="nav-tree-item" data-voice-cat="cloned" onclick="navVoicesCat('cloned')"><span class="mdi mdi-microphone-variant"></span> Cloned <span class="ntc" id="ntc-cloned"></span></div>
<div class="nav-tree-item" data-voice-cat="designed" onclick="navVoicesCat('designed')"><span class="mdi mdi-auto-fix"></span> Designed <span class="ntc" id="ntc-designed"></span></div>
<div class="nav-tree-item" data-voice-cat="favorites" onclick="navVoicesCat('favorites')"><span class="mdi mdi-star-outline"></span> Favorites <span class="ntc" id="ntc-favorites"></span></div>
<div class="nav-tree-item" data-voice-cat="hidden" onclick="navVoicesCat('hidden')"><span class="mdi mdi-eye-off-outline"></span> Hidden <span class="ntc" id="ntc-hidden"></span></div>
<div class="nav-tree-item" data-voice-cat="tools" onclick="navVoicesCat('tools')"><span class="mdi mdi-wrench-outline"></span> Library tools</div>
</div>
<div class="nav-item" data-nav-section="s-clone" onclick="navTo('s-clone')"> <span class="nav-icon"><span class="mdi mdi-microphone-variant"></span></span> Clone a Voice</div>
<div class="nav-item" data-nav-section="s-design" onclick="navTo('s-design')"> <span class="nav-icon"><span class="mdi mdi-auto-fix"></span></span> Design a Voice</div>
<div class="nav-item" data-nav-section="s-studio" onclick="navTo('s-studio')"> <span class="nav-icon"><span class="mdi mdi-earth"></span></span> Get Voices Online</div>
<div class="nav-item" data-nav-section="s-tryout" onclick="navTo('s-tryout')"> <span class="nav-icon"><span class="mdi mdi-play"></span></span> Try It Out</div>
<div class="nav-item nav-tree-head" data-nav-section="s-rehearser" id="nav-rehearser-head" onclick="navTo('s-rehearser')">
<span class="nav-icon"><span class="mdi mdi-theater"></span></span>
<span class="nav-label">Script Rehearser</span>
<span class="nav-chevron" id="nav-rehearser-chevron" onclick="event.stopPropagation();toggleNavTree('nav-rehearser-tree','nav-rehearser-chevron')"><span class="mdi mdi-chevron-down"></span></span>
</div>
<div class="nav-tree" id="nav-rehearser-tree">
<div class="nav-tree-item is-active" data-rehearser-phase="1" onclick="navRehearserPhase(1)"><span class="mdi mdi-bookshelf"></span> Library</div>
<div class="nav-tree-item" data-rehearser-phase="2" onclick="navRehearserPhase(2)"><span class="mdi mdi-account-group-outline"></span> Cast</div>
<div class="nav-tree-item" data-rehearser-phase="3" onclick="navRehearserPhase(3)"><span class="mdi mdi-theater"></span> Stage</div>
<div class="nav-tree-item" data-rehearser-phase="4" onclick="navRehearserPhase(4)"><span class="mdi mdi-check-circle-outline"></span> Summary</div>
<div class="nav-tree-item" id="nav-reh-impex" onclick="navRehearserImpEx()"><span class="mdi mdi-swap-vertical"></span> Import / Export</div>
</div>
<div class="nav-item" data-nav-section="s-reader" onclick="navTo('s-reader')"> <span class="nav-icon"><span class="mdi mdi-book-open-page-variant-outline"></span></span> Read Aloud</div>
<div class="nav-item" data-nav-section="s-conversation" onclick="navTo('s-conversation')"> <span class="nav-icon"><span class="mdi mdi-forum-outline"></span></span> Conversation</div>
<div class="nav-item" data-nav-section="s-performance" onclick="navTo('s-performance')"> <span class="nav-icon"><span class="mdi mdi-speedometer"></span></span> Benchmark</div>
<div class="nav-group-label" style="margin-top:6px">Setup</div>
<div class="nav-item nav-tree-head" data-nav-section="s-llms" id="nav-engines-head" onclick="navTo('s-llms')">
<span class="nav-icon"><span class="mdi mdi-brain"></span></span>
<span class="nav-label">Engines</span>
<span class="nav-chevron" id="nav-engines-chevron" onclick="event.stopPropagation();toggleNavTree('nav-engines-tree','nav-engines-chevron')"><span class="mdi mdi-chevron-down"></span></span>
</div>
<div class="nav-tree" id="nav-engines-tree">
<div class="nav-tree-item is-active" data-engines-cat="llm" onclick="navEnginesCat('llm')"><span class="mdi mdi-brain"></span> Language Models</div>
<div class="nav-tree-item" data-engines-cat="stt" onclick="navEnginesCat('stt')"><span class="mdi mdi-ear-hearing"></span> Speech to Text</div>
<div class="nav-tree-item" data-engines-cat="tts" onclick="navEnginesCat('tts')"><span class="mdi mdi-text-to-speech"></span> Text to Speech</div>
</div>
<div class="nav-item" data-nav-section="s-routing" onclick="navTo('s-routing')"> <span class="nav-icon"><span class="mdi mdi-swap-horizontal"></span></span> App Routing</div>
<div class="nav-item" data-nav-section="s-connect" onclick="navTo('s-connect')"> <span class="nav-icon"><span class="mdi mdi-api"></span></span> Connect Apps</div>
<div class="nav-item nav-tree-head" data-nav-section="s-settings" id="nav-settings-head" onclick="navTo('s-settings')">
<span class="nav-icon"><span class="mdi mdi-cog-outline"></span></span>
<span class="nav-label">Settings</span>
<span class="nav-chevron" id="nav-settings-chevron" onclick="event.stopPropagation();toggleNavTree('nav-settings-tree','nav-settings-chevron')"><span class="mdi mdi-chevron-down"></span></span>
</div>
<div class="nav-tree" id="nav-settings-tree">
<div class="nav-tree-item" data-settings-cat="general" onclick="navSettingsCat('general')"><span class="mdi mdi-tune-vertical"></span> General</div>
<div class="nav-tree-item" data-settings-cat="connections" onclick="navSettingsCat('connections')"><span class="mdi mdi-lan-connect"></span> Connections</div>
<div class="nav-tree-item" data-settings-cat="playback" onclick="navSettingsCat('playback')"><span class="mdi mdi-speaker-outline"></span> Playback</div>
<div class="nav-tree-item" data-settings-cat="captures" onclick="navSettingsCat('captures')"><span class="mdi mdi-record-circle-outline"></span> Captures</div>
<div class="nav-tree-item" data-settings-cat="payloads" onclick="navSettingsCat('payloads')"><span class="mdi mdi-code-json"></span> Payloads</div>
<div class="nav-tree-item" data-settings-cat="storage" onclick="navSettingsCat('storage')"><span class="mdi mdi-folder-outline"></span> Storage</div>
<div class="nav-tree-item" data-settings-cat="apikeys" onclick="navSettingsCat('apikeys')"><span class="mdi mdi-key-outline"></span> API Keys</div>
<div class="nav-tree-item" data-settings-cat="backup" onclick="navSettingsCat('backup')"><span class="mdi mdi-backup-restore"></span> Backup</div>
<div class="nav-tree-item" data-settings-cat="logs" onclick="navSettingsCat('logs')"><span class="mdi mdi-text-box-outline"></span> Logs</div>
<div class="nav-tree-item" data-settings-cat="about" onclick="navSettingsCat('about')"><span class="mdi mdi-information-outline"></span> About</div>
</div>
</nav>
<div class="sidebar-footer">
<button id="theme-btn" title="Switch theme" style="display:none"><span class="mdi mdi-weather-sunny"></span></button>
</div>
</aside>
<!-- Main content — sections injected by loader.js -->
<main id="main-content" tabindex="-1">
<!-- ── Skeleton loading view ─────────────────────────────────────────── -->
<!-- Visible immediately; loader.js fades it out when real content is ready -->
<div id="app-skeleton">
<!-- Page head -->
<div class="sk-head">
<div class="sk sk-icon"></div>
<div class="sk-title">
<div class="sk" style="width:160px;height:22px"></div>
<div class="sk" style="width:280px;height:13px"></div>
</div>
</div>
<!-- Toolbar row -->
<div class="sk-toolbar">
<div class="sk" style="width:48px;height:24px;border-radius:12px"></div>
<div class="sk" style="width:200px;height:30px;border-radius:6px"></div>
<div class="sk" style="width:80px;height:30px;border-radius:6px;margin-left:auto"></div>
<div class="sk" style="width:80px;height:30px;border-radius:6px"></div>
</div>
<!-- Voices workbench skeleton (list + inspector, matches real My Voices layout) -->
<div class="sk-workbench">
<div class="sk-list">
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:75%;height:13px"></div><div class="sk" style="width:50%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:65%;height:13px"></div><div class="sk" style="width:40%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:80%;height:13px"></div><div class="sk" style="width:45%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:55%;height:13px"></div><div class="sk" style="width:35%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:70%;height:13px"></div><div class="sk" style="width:48%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:60%;height:13px"></div><div class="sk" style="width:42%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:85%;height:13px"></div><div class="sk" style="width:38%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:72%;height:13px"></div><div class="sk" style="width:44%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:58%;height:13px"></div><div class="sk" style="width:36%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:78%;height:13px"></div><div class="sk" style="width:52%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
</div>
<div class="sk-insp">
<div class="sk-insp-head">
<div class="sk" style="width:55%;height:20px"></div>
<div class="sk" style="width:35%;height:13px"></div>
<div class="sk-row" style="margin-top:4px"><div class="sk" style="width:60px;height:22px;border-radius:4px"></div><div class="sk" style="width:50px;height:22px;border-radius:4px"></div><div class="sk" style="width:80px;height:22px;border-radius:4px;margin-left:auto"></div></div>
</div>
<div class="sk" style="height:1px;border-radius:0"></div>
<div class="sk-insp-head">
<div class="sk" style="width:40%;height:14px"></div>
<div class="sk" style="width:100%;height:56px;border-radius:6px"></div>
</div>
<div class="sk-insp-head">
<div class="sk" style="width:30%;height:14px"></div>
<div class="sk" style="width:100%;height:36px;border-radius:6px"></div>
</div>
<div class="sk-insp-head">
<div class="sk" style="width:45%;height:14px"></div>
<div class="sk" style="width:100%;height:36px;border-radius:6px"></div>
</div>
</div>
</div>
</div>
<!-- /app-skeleton -->
<section class="page-section" id="s-voices" style="display:none"></section>
<section class="page-section" id="s-clone" style="display:none"></section>
<section class="page-section" id="s-design" style="display:none"></section>
<section class="page-section" id="s-studio" style="display:none"></section>
<section class="page-section" id="s-tryout" style="display:none"></section>
<section class="page-section" id="s-performance" style="display:none"></section>
<section class="page-section" id="s-routing" style="display:none"></section>
<section class="page-section" id="s-connect" style="display:none"></section>
<section class="page-section" id="s-settings" style="display:none"></section>
<section class="page-section" id="s-llms" style="display:none"></section>
<section class="page-section" id="s-rehearser" style="display:none"></section>
<section class="page-section" id="s-reader" style="display:none"></section>
<section class="page-section" id="s-conversation" style="display:none"></section>
</main>
</div><!-- /app-shell -->
<div id="toast" aria-live="polite" aria-atomic="true"></div>
<div id="status-bar" role="status" aria-live="polite" aria-atomic="true">Loading…</div>
<!-- WaveSurfer — self-hosted, loaded synchronously (voice waveform requires it) -->
<script src="/static/vendor/wavesurfer.min.js"></script>
<script src="/static/vendor/wavesurfer-regions.min.js"></script>
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
<script src="/static/loader.js?v=1.8.0-2"></script>
</body>
</html>