Enhance TTS functionality and security settings

- Integrated Google TTS languages from a separate module for better maintainability.
- Updated local device voice fetching to support both macOS and Windows, improving cross-platform compatibility.
- Enhanced dashboard route protection by adding dynamic settings for login requirements and tunnel access.
- Introduced UI elements for managing security settings related to API key requirements and dashboard access via tunnel.
- Added default TTS response example in the media provider page for better user guidance.
- Updated constants to reflect changes in TTS provider configurations.

This commit improves the overall user experience and security of the TTS features.
This commit is contained in:
decolua committed 2026-04-11 14:56:35 +07:00
1 parent 875a1282ea
commit b3feb96740
10 files changed
+315 -67

No files matched your search

@@ -22,6 +22,13 @@ function Row({ label, children }) {
);
}
const DEFAULT_TTS_RESPONSE_EXAMPLE = `// Audio will appear here after running.
// Example JSON response (response_format=json):
{
"format": "mp3",
"audio": "//NExAANaAIIAUAAANNNNNNNN..." // base64 encoded MP3
}`;
const DEFAULT_RESPONSE_EXAMPLE = `{
"object": "list",
"data": [{
@@ -296,12 +303,15 @@ function TtsExampleCard({ providerId }) {
const voiceKey = config.voiceKey || providerId;
const voices = getModelsByProviderId(voiceKey).filter((m) => m.type === "tts");
if (voices.length) {
if (config.hasLanguageDropdown) {
// Google TTS: just set voice
setSelectedVoice(voices[0].id);
setSelectedVoiceName(voices[0].name || voices[0].id);
if (config.hasBrowseButton) {
// Google TTS: pre-select "en" (English) as default, show as single voice chip
const defaultVoice = voices.find((v) => v.id === "en") || voices[0];
setSelectedLang(defaultVoice.id);
setSelectedVoice(defaultVoice.id);
setSelectedVoiceName(defaultVoice.name);
setCountryVoices([{ id: defaultVoice.id, name: defaultVoice.name }]);
} else {
// OpenAI: set voice chips
// OpenAI: set voice chips directly (no language picker)
setCountryVoices(voices);
setSelectedVoice(voices[0].id);
setSelectedVoiceName(voices[0].name || voices[0].id);
@@ -319,15 +329,27 @@ function TtsExampleCard({ providerId }) {
if (languages.length) return; // already loaded
setModalLoading(true);
try {
// Use provider-specific apiEndpoint if available, else default to edge-tts voices API
const url = config.apiEndpoint
? config.apiEndpoint
: `/api/media-providers/tts/voices?provider=${providerId === "local-device" ? "local-device" : "edge-tts"}`;
const r = await fetch(url);
const d = await r.json();
if (d.error) { setModalError(d.error); return; }
setLanguages(d.languages || []);
setByLang(d.byLang || {});
if (config.voiceSource === "hardcoded") {
// Build languages/byLang from static providerModels data
const voiceKey = config.voiceKey || providerId;
const voices = getModelsByProviderId(voiceKey).filter((m) => m.type === "tts");
const byLangMap = {};
for (const v of voices) {
if (!byLangMap[v.id]) byLangMap[v.id] = { code: v.id, name: v.name, voices: [{ id: v.id, name: v.name }] };
}
setByLang(byLangMap);
setLanguages(Object.values(byLangMap).sort((a, b) => a.name.localeCompare(b.name)));
} else {
// Use provider-specific apiEndpoint if available, else default to edge-tts voices API
const url = config.apiEndpoint
? config.apiEndpoint
: `/api/media-providers/tts/voices?provider=${providerId === "local-device" ? "local-device" : "edge-tts"}`;
const r = await fetch(url);
const d = await r.json();
if (d.error) { setModalError(d.error); return; }
setLanguages(d.languages || []);
setByLang(d.byLang || {});
}
} catch (e) {
setModalError(e.message);
} finally {
@@ -472,7 +494,7 @@ function TtsExampleCard({ providerId }) {
className="flex items-center gap-1 text-xs px-2.5 py-1.5 rounded-lg border border-border text-text-muted hover:text-primary hover:border-primary/40 transition-colors shrink-0"
>
<span className="material-symbols-outlined text-[14px]">language</span>
Browse
Select language
</button>
</div>
</Row>
@@ -647,7 +669,10 @@ function TtsExampleCard({ providerId }) {
)}
</div>
) : (
<p className="text-xs text-text-muted opacity-60">Audio will appear here after running.</p>
<div>
<span className="text-xs font-semibold text-text-muted uppercase tracking-wider">Response</span>
<pre className="mt-1.5 bg-sidebar rounded-lg px-3 py-2.5 text-xs font-mono text-text-main overflow-x-auto whitespace-pre opacity-50">{DEFAULT_TTS_RESPONSE_EXAMPLE}</pre>
</div>
)}
</div>
</Card>