diff --git a/static/nav.js b/static/nav.js
index 58184eb..4a9195c 100644
--- a/static/nav.js
+++ b/static/nav.js
@@ -37,12 +37,20 @@
document.querySelectorAll('[data-nav-section]').forEach(function (item) {
item.classList.toggle('active', item.dataset.navSection === sectionId);
});
- // Expand tree only when voices section is active
- var tree = document.getElementById('nav-voices-tree');
- var chevron = document.getElementById('nav-voices-chevron');
+
+ // Voices nav tree
+ var voicesTree = document.getElementById('nav-voices-tree');
+ var voicesChevron = document.getElementById('nav-voices-chevron');
var isVoices = sectionId === 's-voices';
- if (tree) tree.classList.toggle('open', isVoices);
- if (chevron) chevron.classList.toggle('open', isVoices);
+ 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;
@@ -87,6 +95,30 @@
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');
diff --git a/static/sections/s-settings.html b/static/sections/s-settings.html
index 4f2527d..ca3365c 100644
--- a/static/sections/s-settings.html
+++ b/static/sections/s-settings.html
@@ -13,15 +13,30 @@
Settings
-
Configure the service URLs you use. Advanced payloads, folders, and keys are tucked away below.
+
Configure the service URLs you use. Advanced payloads, folders, and keys are below.
Local stack
+
+
+
General
+
+
+
+
+ Switch between light and dark interface.
+
+
+
+
-
-
Core connections
-
These are the endpoints you change most often. NVIDIA TTS and STT are grouped separately.
+
+
Connections
+
Service URLs for each backend. Change these first when setting up.
@@ -131,8 +146,9 @@
-
-
Playback behavior
+
+
Playback
+
Controls how previews play and how OpenAI-compatible requests are shaped.
@@ -156,11 +172,17 @@
+
+
+
+
+
+
-
+
- Advanced request payloads
+ Payloads
JSON extras sent to each backend
@@ -209,10 +231,10 @@
-
+
- Voice folders
+ Storage
container paths and Portainer volume mounts
@@ -231,10 +253,10 @@
-
+
- API keys
+ API Keys
usually empty for local containers
@@ -263,14 +285,8 @@
-
-
-
-
-
-
-
+
Backup & restore
Export all voices and settings as a ZIP for backup or migration. Import to restore. API keys are excluded from exports.
@@ -283,5 +299,45 @@
+
+
+
Logs
+
Recent server activity. Useful for debugging backend connections and API errors.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Click Refresh to load logs
+
+
+
+
+
+
About
+
+
TTS Voice Creator
+
Clone, design, and deploy custom voices using local AI backends. Compatible with Qwen3-TTS, Kokoro FastAPI, NVIDIA Magpie, and any OpenAI-compatible TTS/STT endpoint.