tts-voice-creator-clone-and.../static/nav.js
mARTin-B78 a0d94b140e Fix clone UX: sample text, live monitor, quality, STT picker, OGG accept
1. Sample text: expose initCloneSampleText as a window function and call
   it from nav.js runSideEffects when the clone section is activated,
   ensuring the textarea is always populated even if the IIFE ran before
   the element existed.

2. Better sample texts: all 8 languages rewritten to ~38 words / ~15 s,
   first-person, phonetically rich, proper Unicode diacritics.

3. Live mic monitor: a level-meter (18-bar) + scrolling oscilloscope
   canvas (ring-buffer, 300 px, colour-coded) added to the microphone
   card.  "Check level" / "Stop monitor" buttons start/stop it
   independently; clicking Record starts it automatically.
   Uses raw mic constraints (no echo-cancel / AGC) for cleaner voice clone
   audio.  Mic gain slider and dB readout included.

4. Recording quality: MediaRecorder now requests audioBitsPerSecond:256000
   in both voice-clone.js and stt.js.

5. STT engine picker: Recognition engine <select> + Refresh button added
   above the Auto-transcribe button in Step 3.  refreshSttBackends() now
   syncs both stt-tts-stt-backend and clone-stt-backend.  The transcribe
   call passes the chosen backend to /api/transcribe.

6. File input: explicit extension list added to accept= for OGG/OPUS.

7. CSS: .mic-live-wave style added (dark/light theme variants).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:58:28 +02:00

215 lines
9.4 KiB
JavaScript

(function () {
'use strict';
const TAB_SECTION_MAP = {
library: 's-voices',
source: 's-clone',
save: 's-clone',
design: 's-design',
custom: 's-design',
getvoices: 's-studio',
generation: 's-tryout',
'stt-tts': 's-tryout',
performance: 's-performance',
routing: 's-routing',
integrations: 's-connect',
howto: 's-connect',
settings: 's-settings',
llms: 's-llms'
};
const SECTIONS = ['s-voices', 's-clone', 's-design', 's-studio', 's-tryout', 's-performance', 's-routing', 's-connect', 's-settings', 's-llms', 's-conversation'];
function runSideEffects(name) {
if ((name === 'source' || name === 'save') && typeof initCloneSampleText === 'function') initCloneSampleText();
if (name === 'library' && typeof loadVoiceLibrary === 'function') loadVoiceLibrary();
if ((name === 'integrations' || name === 'howto') && typeof renderIntegrationSnippets === 'function') {
if (typeof loadVoiceLibrary === 'function' && !(window._voices && window._voices.length)) loadVoiceLibrary();
renderIntegrationSnippets();
}
if (name === 'routing' && typeof loadRoutingTab === 'function') loadRoutingTab();
if (name === 'getvoices' && typeof loadGetVoices === 'function') loadGetVoices();
}
function applySettingsPage(cat) {
document.querySelectorAll('.s-settings-page').forEach(function (p) {
p.classList.toggle('is-active', p.dataset.page === cat);
});
document.querySelectorAll('[data-settings-cat]').forEach(function (el) {
el.classList.toggle('is-active', el.dataset.settingsCat === cat);
});
}
function showSection(sectionId) {
localStorage.setItem('ttsvc_section', sectionId);
SECTIONS.forEach(function (id) {
var el = document.getElementById(id);
if (el) el.classList.toggle('is-active', id === sectionId);
});
document.querySelectorAll('[data-nav-section]').forEach(function (item) {
item.classList.toggle('active', item.dataset.navSection === sectionId);
});
// Voices nav tree
var voicesTree = document.getElementById('nav-voices-tree');
var voicesChevron = document.getElementById('nav-voices-chevron');
if (voicesTree) voicesTree.classList.toggle('open', sectionId === 's-voices');
if (voicesChevron) voicesChevron.classList.toggle('open', sectionId === 's-voices');
// Settings nav tree
var settingsTree = document.getElementById('nav-settings-tree');
var settingsChevron = document.getElementById('nav-settings-chevron');
if (settingsTree) settingsTree.classList.toggle('open', sectionId === 's-settings');
if (settingsChevron) settingsChevron.classList.toggle('open', sectionId === 's-settings');
// Engines nav tree
var enginesTree = document.getElementById('nav-engines-tree');
var enginesChevron = document.getElementById('nav-engines-chevron');
if (enginesTree) enginesTree.classList.toggle('open', sectionId === 's-llms');
if (enginesChevron) enginesChevron.classList.toggle('open', sectionId === 's-llms');
// When entering settings, show the active sub-page (default: connections)
if (sectionId === 's-settings') {
applySettingsPage(window._settingsSidebarCat || 'connections');
}
// When entering engines, show the active sub-page (default: llm)
if (sectionId === 's-llms') {
applyEnginesPage(window._enginesSidebarCat || 'llm');
}
var main = document.getElementById('main-content');
if (main) main.scrollTop = 0;
}
window.navTo = function (sectionId) {
showSection(sectionId);
var tabName = Object.keys(TAB_SECTION_MAP).find(function (k) {
return TAB_SECTION_MAP[k] === sectionId;
});
if (tabName) runSideEffects(tabName);
};
window.switchTab = function (name) {
var sectionId = TAB_SECTION_MAP[name];
if (sectionId) showSection(sectionId);
runSideEffects(name);
return true;
};
// ── Voice tree sub-navigation ─────────────────────────────────────────────
window._voiceSidebarCat = 'all';
window.navVoicesCat = function (cat) {
navTo('s-voices');
window._voiceSidebarCat = cat;
document.querySelectorAll('[data-voice-cat]').forEach(function (el) {
el.classList.toggle('is-active', el.dataset.voiceCat === cat);
});
if (typeof renderVoiceList === 'function') renderVoiceList();
};
window.updateVoiceTree = function (voices) {
if (!voices) return;
function n(fn) { return voices.filter(fn).length; }
function set(id, v) { var el = document.getElementById(id); if (el) el.textContent = v || ''; }
set('ntc-all', n(function () { return true; }));
set('ntc-cloned', n(function (v) { return v.has_ref; }));
set('ntc-designed', n(function (v) { return !v.has_ref; }));
set('ntc-favorites',n(function (v) { return (v.rating || 0) >= 4; }));
set('ntc-hidden', n(function (v) { return v.enabled === false; }));
set('nav-voices-count', voices.length);
};
// ── Settings tree sub-navigation ──────────────────────────────────────────
window._settingsSidebarCat = 'connections';
window.navSettingsCat = function (cat) {
window._settingsSidebarCat = cat;
localStorage.setItem('ttsvc_settings_cat', cat);
navTo('s-settings'); // showSection will call applySettingsPage(cat)
if (cat === 'logs' && typeof loadSettingsLogs === 'function') loadSettingsLogs();
if (cat === 'about' && typeof renderSettingsAbout === 'function') renderSettingsAbout();
};
// ── Engines tree sub-navigation ───────────────────────────────────────────
function applyEnginesPage(cat) {
document.querySelectorAll('.s-engines-page').forEach(function (p) {
p.classList.toggle('is-active', p.dataset.page === cat);
});
document.querySelectorAll('[data-engines-cat]').forEach(function (el) {
el.classList.toggle('is-active', el.dataset.enginesCat === cat);
});
}
window._enginesSidebarCat = 'llm';
window.navEnginesCat = function (cat) {
window._enginesSidebarCat = cat;
localStorage.setItem('ttsvc_engines_cat', cat);
navTo('s-llms'); // showSection will call applyEnginesPage(cat)
if ((cat === 'tts' || cat === 'stt') && typeof loadLocalContainers === 'function') loadLocalContainers();
};
// Restore last-used section from localStorage (fallback: My Voices)
var _savedSection = localStorage.getItem('ttsvc_section');
window._settingsSidebarCat = localStorage.getItem('ttsvc_settings_cat') || 'connections';
window._enginesSidebarCat = localStorage.getItem('ttsvc_engines_cat') || 'llm';
var _startSection = (_savedSection && SECTIONS.includes(_savedSection)) ? _savedSection : 's-voices';
showSection(_startSection);
var _startTab = Object.keys(TAB_SECTION_MAP).find(function (k) { return TAB_SECTION_MAP[k] === _startSection; }) || 'library';
runSideEffects(_startTab);
// ── Mobile sidebar drawer ────────────────────────────────────────────────
var sidebar = document.getElementById('sidebar');
var backdrop = document.getElementById('sidebar-backdrop');
var toggleBtn = document.getElementById('sidebar-toggle');
function openSidebar() {
if (!sidebar) return;
sidebar.classList.add('open');
if (backdrop) backdrop.classList.add('open');
document.body.style.overflow = 'hidden'; // prevent scroll behind drawer
}
function closeSidebar() {
if (!sidebar) return;
sidebar.classList.remove('open');
if (backdrop) backdrop.classList.remove('open');
document.body.style.overflow = '';
}
if (toggleBtn) toggleBtn.addEventListener('click', openSidebar);
if (backdrop) backdrop.addEventListener('click', closeSidebar);
// Close drawer when a nav item is tapped on mobile
document.querySelectorAll('#sidebar .nav-item, #sidebar .nav-tree-item').forEach(function (el) {
el.addEventListener('click', function () {
if (window.innerWidth <= 767) closeSidebar();
});
});
// ── Mobile voice inspector: back button ──────────────────────────────────
// When a voice row is selected on mobile, show inspector full-height.
// The inspector JS will call window.onMobileInspectorOpen/Close as hooks.
window.onMobileInspectorOpen = function () {
var wb = document.querySelector('.voices-workbench');
if (!wb || window.innerWidth > 767) return;
wb.classList.add('mobile-inspector-open');
// Inject back button if not already there
var insp = document.getElementById('voices-inspector');
if (insp && !insp.querySelector('.insp-mobile-back')) {
var back = document.createElement('div');
back.className = 'insp-mobile-back';
back.innerHTML = '<span class="mdi mdi-arrow-left"></span> All voices';
back.addEventListener('click', function () { window.onMobileInspectorClose && window.onMobileInspectorClose(); });
insp.insertBefore(back, insp.firstChild);
}
};
window.onMobileInspectorClose = function () {
var wb = document.querySelector('.voices-workbench');
if (wb) wb.classList.remove('mobile-inspector-open');
var back = document.querySelector('.insp-mobile-back');
if (back) back.remove();
};
})();