- Sidebar: Settings → nav-tree-head with sub-items (General, Connections, Playback, Payloads, Storage, API Keys, Backup, Logs, About) - nav.js: navSettingsCat() scrolls to section, expands tree on activate - General: theme select synced with applyTheme, surfaces dark/light toggle - Logs: /api/logs endpoint (300-entry circular buffer), refresh/clear/ auto-refresh every 3 s, level filters (All/Error/Warning/Info) - About: backend availability chips from _ttsBackends, tech stack tags - server.py: _BufferHandler attaches to root logger, /api/logs GET+DELETE - Fix duplicate toast on save, guard removed settings-btn reference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
126 lines
4.9 KiB
JavaScript
126 lines
4.9 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',
|
|
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-routing', 's-connect', 's-settings', 's-llms'];
|
|
|
|
function runSideEffects(name) {
|
|
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 showSection(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');
|
|
var isVoices = sectionId === 's-voices';
|
|
if (voicesTree) voicesTree.classList.toggle('open', isVoices);
|
|
if (voicesChevron) voicesChevron.classList.toggle('open', isVoices);
|
|
|
|
// Settings nav tree
|
|
var settingsTree = document.getElementById('nav-settings-tree');
|
|
var settingsChevron = document.getElementById('nav-settings-chevron');
|
|
var isSettings = sectionId === 's-settings';
|
|
if (settingsTree) settingsTree.classList.toggle('open', isSettings);
|
|
if (settingsChevron) settingsChevron.classList.toggle('open', isSettings);
|
|
|
|
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 = null;
|
|
|
|
window.navSettingsCat = function (cat) {
|
|
navTo('s-settings');
|
|
window._settingsSidebarCat = cat;
|
|
document.querySelectorAll('[data-settings-cat]').forEach(function (el) {
|
|
el.classList.toggle('is-active', el.dataset.settingsCat === cat);
|
|
});
|
|
if (cat === 'logs' && typeof loadSettingsLogs === 'function') loadSettingsLogs();
|
|
// Scroll inside main-content to the target section
|
|
var target = document.getElementById('s-settings-' + cat);
|
|
if (target) {
|
|
setTimeout(function () {
|
|
var main = document.getElementById('main-content');
|
|
if (main) {
|
|
var mainRect = main.getBoundingClientRect();
|
|
var targetRect = target.getBoundingClientRect();
|
|
main.scrollTo({ top: main.scrollTop + targetRect.top - mainRect.top - 16, behavior: 'smooth' });
|
|
}
|
|
}, 50);
|
|
}
|
|
};
|
|
|
|
// Show default section on load
|
|
showSection('s-voices');
|
|
runSideEffects('library');
|
|
})();
|