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>
215 lines
9.4 KiB
JavaScript
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();
|
|
};
|
|
})();
|