Security fixes: - Block /proc /sys /dev /run /boot in /api/browse-dirs (path traversal) - Verify yt-dlp output stays inside TEMP_DIR before registration - Remove Access-Control-Allow-Origin: * from /api/proxy-audio - TTL-based temp file registry (default 2h) to prevent disk fill Performance: - Cache settings + routing rules in memory (mtime-checked); eliminates per-request disk reads on every TTS call UI: - Add container name (optional) field to Docker stack TTS/STT engine cards (Qwen3 Voice Clone, Voice Design, Custom Voice, Streaming, NVIDIA Magpie, Parakeet) — enables Stop/Start/Restart buttons on all engine cards, matching the existing Other Local TTS/STT cards Refactor — backend: - server.py: 5560 lines → 43-line entry point - core/ package: constants, registry, validation, docker_client, config, routing, audio, voice, presets, tts_helpers - routes/ package: admin, settings, library, stt, sources, docker, tts, conversation (FastAPI APIRouter modules) - Dockerfile + docker-compose.yml updated to include core/ and routes/ Refactor — frontend: - static/app.js: 8744 lines → 16 modules in static/js/ utils, voice-inspector, voice-sources, integrations, routing, settings, voice-clone, voice-library, tts-preview, benchmark, stt, init, engines, ai-backends, generation, conversation - static/loader.js updated to load modules sequentially Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 lines
2.1 KiB
JavaScript
50 lines
2.1 KiB
JavaScript
// ── Init ──────────────────────────────────────────────────────────────────
|
|
|
|
initBenchmarkSampleControls();
|
|
// Sync visible preview-text input with hidden benchmark-sample-text
|
|
(function syncPreviewInput() {
|
|
const visible = $('vl-preview-sample');
|
|
const hidden = $('benchmark-sample-text');
|
|
if (!visible) return;
|
|
const stored = localStorage.getItem(BENCHMARK_SAMPLE_STORAGE_KEY);
|
|
visible.value = stored || DEFAULT_BENCHMARK_SAMPLE_TEXT;
|
|
if (hidden) hidden.value = visible.value;
|
|
visible.addEventListener('input', () => {
|
|
if (hidden) hidden.value = visible.value;
|
|
localStorage.setItem(BENCHMARK_SAMPLE_STORAGE_KEY, visible.value.trim());
|
|
});
|
|
})();
|
|
|
|
// Sync flag button to current preview text language (best-match)
|
|
(function syncPreviewLangBtn() {
|
|
const ta = $('vl-preview-sample');
|
|
const btn = $('vl-preview-lang-btn');
|
|
if (!ta || !btn) return;
|
|
const cur = ta.value.trim();
|
|
const matched = Object.keys(VL_SAMPLE_TEXTS).find(l => VL_SAMPLE_TEXTS[l] === cur) || 'EN';
|
|
const cc = (_VL_LANG_FLAG[matched] || 'gb').toLowerCase();
|
|
btn.innerHTML = `<span class="fi fi-${cc}"></span>`;
|
|
btn.title = (LANGUAGE_LABELS[matched] || matched) + ' — click to change sample language';
|
|
btn.dataset.lang = matched;
|
|
})();
|
|
|
|
// Synth mode segmented control (preview text vs. reference transcript)
|
|
(function initSynthMode() {
|
|
const seg = $('vl-synth-mode-seg');
|
|
const wrap = $('vl-preview-text-wrap');
|
|
if (!seg) return;
|
|
seg.querySelectorAll('.vl-synth-seg-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
seg.querySelectorAll('.vl-synth-seg-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
if (wrap) wrap.classList.toggle('hidden', btn.dataset.mode === 'transcript');
|
|
});
|
|
});
|
|
})();
|
|
loadSettings().then(() => {
|
|
refreshSttBackends();
|
|
renderIntegrationSnippets();
|
|
if (!localStorage.getItem(SETTINGS_SEEN_KEY)) openSettings(true);
|
|
}).catch(e => status('Settings load failed: ' + e.message));
|
|
|