tts: hotfix qwen 503s and primer noise (5 stale defaults, AudioContext primer, normalizeModelVoice)
This commit is contained in:
parent
9e36a1b6c2
commit
29c400bdb4
3 changed files with 50 additions and 24 deletions
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import { initializeChunkFiveFont } from "./ui-themes.js";
|
||||
import { getUIText } from "./ui-translations.js";
|
||||
import { handleTTS, downloadAudio, generateTitleForTTS, getTTSMode, setTTSMode, primeAudioPlayback } from "./tts.js";
|
||||
import { handleTTS, downloadAudio, generateTitleForTTS, getTTSMode, setTTSMode, primeAudioPlayback, normalizeModelVoice } from "./tts.js";
|
||||
import { VOICES_API_URL } from "./config.js";
|
||||
|
||||
// Function to fetch and populate voice dropdown
|
||||
|
|
@ -356,14 +356,15 @@ export function openTTSModal() {
|
|||
const speed = parseFloat(speedSlider.value);
|
||||
|
||||
// Parse model:voice format (e.g., "tts-1-f5:aria" or legacy "aria")
|
||||
// Normalize legacy/qwen entries so stale vault data doesn't 503.
|
||||
let model = 'tts-1-f5';
|
||||
let voice = selectedValue;
|
||||
|
||||
if (selectedValue.includes(':')) {
|
||||
const parts = selectedValue.split(':');
|
||||
model = parts[0];
|
||||
voice = parts[1];
|
||||
}
|
||||
({ model, voice } = normalizeModelVoice(model, voice));
|
||||
|
||||
try {
|
||||
const currentMode = getTTSMode();
|
||||
|
|
|
|||
|
|
@ -327,20 +327,42 @@ async function fetchTTSChunk(chunkText, voice, model) {
|
|||
}
|
||||
}
|
||||
|
||||
// Silent 1-frame MP3 (data URI). Used to "prime" audio output inside a user
|
||||
// gesture handler so iOS Safari unlocks audio playback before async work runs.
|
||||
// Without this, audio.play() called after `await` rejects with NotAllowedError.
|
||||
const SILENT_MP3_DATA_URI =
|
||||
"data:audio/mp3;base64,SUQzBAAAAAABEVRYWFgAAAAtAAADY29tbWVudABCaWdTb3VuZEJhbmsuY29tIC8gTGFTb25vdGhlcXVlLm9yZwBURU5DAAAAHQAAA1N3aXRjaCBQbHVzIMKpIE5DSCBTb2Z0d2FyZQBUSVQyAAAABgAAAzIyMzUAVFNTRQAAAA8AAANMYXZmNTcuODMuMTAwAAAAAAAAAAAAAAD/80DEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//MUZAAAAAGkAAAAAAAAA0gAAAAAVVVV";
|
||||
// Legacy voice names from the deprecated Qwen-only era. Anyone with these in
|
||||
// their vault would otherwise hit 503 since the backend routes "tts-1" → qwen
|
||||
// and qwen-tts is not installed in the F5 sidecar.
|
||||
const LEGACY_OPENAI_VOICES = new Set([
|
||||
"alloy", "echo", "fable", "onyx", "nova", "shimmer",
|
||||
"amber", "breeze", "coral", "dawn", "ember", "frost",
|
||||
"glow", "haze", "jade", "kite", "lark", "mist", "nectar",
|
||||
]);
|
||||
|
||||
// Call inside a user-gesture handler before doing any async work, so iOS Safari
|
||||
// unlocks audio output for subsequent .play() calls after `await`.
|
||||
// Normalize a model+voice pair: remap legacy / unknown models to F5, and
|
||||
// remap legacy OpenAI voice names to F5's "aria" default.
|
||||
export function normalizeModelVoice(model, voice) {
|
||||
const isQwen = !model || model === "tts-1" || model.includes("qwen");
|
||||
const normModel = isQwen ? "tts-1-f5" : model;
|
||||
const normVoice = LEGACY_OPENAI_VOICES.has(voice) ? "aria" : voice;
|
||||
return { model: normModel, voice: normVoice };
|
||||
}
|
||||
|
||||
// Call inside a user-gesture handler before doing any async work to unlock iOS
|
||||
// Safari audio output for subsequent .play() calls after `await`. Uses an
|
||||
// AudioContext + zero-gain silent buffer (NOT a "silent" MP3 data URI, which
|
||||
// can contain encoder artifacts the user hears as a pop/buzz before playback).
|
||||
let _audioCtx = null;
|
||||
export function primeAudioPlayback() {
|
||||
try {
|
||||
const a = new Audio(SILENT_MP3_DATA_URI);
|
||||
a.volume = 0;
|
||||
const p = a.play();
|
||||
if (p && p.catch) p.catch(() => {});
|
||||
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||
if (!Ctx) return;
|
||||
if (!_audioCtx) _audioCtx = new Ctx();
|
||||
if (_audioCtx.state === "suspended") _audioCtx.resume();
|
||||
const buffer = _audioCtx.createBuffer(1, 1, _audioCtx.sampleRate || 22050);
|
||||
const source = _audioCtx.createBufferSource();
|
||||
const gain = _audioCtx.createGain();
|
||||
gain.gain.value = 0;
|
||||
source.buffer = buffer;
|
||||
source.connect(gain).connect(_audioCtx.destination);
|
||||
source.start(0);
|
||||
} catch (e) {
|
||||
console.warn("primeAudioPlayback failed:", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
|
||||
import hljs from "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/es/highlight.min.js";
|
||||
import { setSystemMessageAppend, isTTSEnabled } from "./config.js";
|
||||
import { normalizeModelVoice } from "./tts.js";
|
||||
import { extractWebpageContent, fitPageContent, buildPageAnalysisPrompt, parsePageAnalysis, buildAnalysisContext } from "./content.js";
|
||||
import { computePageIntelligence, formatPageIntelligence } from "./page-intelligence.js";
|
||||
import { loadSiteJourney, recordPageVisit, formatJourneyContext, savePageSummary, getRelevantPageSummaries, saveConversationSummary, saveSiteLinks } from "./storage.js";
|
||||
|
|
@ -54,15 +55,17 @@ async function ensureCryptoJS() {
|
|||
// Get USE_CUSTOM_STYLING from window or default
|
||||
const _USE_CUSTOM_STYLING = window.UNCLOSEAI_CUSTOM_STYLING !== false;
|
||||
|
||||
// Helper function to parse model:voice format
|
||||
// Helper function to parse model:voice format. Normalizes legacy/qwen models
|
||||
// and legacy OpenAI voice names so stale vault entries don't hit 503.
|
||||
function parseVoiceSelection(selectedValue) {
|
||||
// Check if format is "model:voice" or legacy "voice"
|
||||
let model = "tts-1-f5";
|
||||
let voice = selectedValue;
|
||||
if (selectedValue.includes(":")) {
|
||||
const [model, voice] = selectedValue.split(":");
|
||||
return { model, voice };
|
||||
const [m, v] = selectedValue.split(":");
|
||||
model = m;
|
||||
voice = v;
|
||||
}
|
||||
// Legacy format - assume tts-1-f5 model
|
||||
return { model: "tts-1-f5", voice: selectedValue };
|
||||
return normalizeModelVoice(model, voice);
|
||||
}
|
||||
|
||||
// Import required functions dynamically to avoid circular dependencies
|
||||
|
|
@ -1498,7 +1501,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
|
||||
userTtsBtn.onclick = async () => {
|
||||
const modalVoiceSelect = document.getElementById("hermes-voice-select");
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1:alloy";
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1-f5:aria";
|
||||
|
||||
// If audio is already playing, pause it
|
||||
if (userAudio && !userAudio.paused) {
|
||||
|
|
@ -1692,7 +1695,7 @@ async function openUncloseaiEmbeddedModalNew() {
|
|||
const modalVoiceSelect = document.getElementById(
|
||||
"hermes-voice-select",
|
||||
);
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1:alloy";
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1-f5:aria";
|
||||
|
||||
// If audio is already playing, pause it
|
||||
if (currentAudio && !currentAudio.paused) {
|
||||
|
|
@ -2167,7 +2170,7 @@ You have complete knowledge of this page content and can reference any details,
|
|||
const modalVoiceSelect = document.getElementById(
|
||||
"hermes-voice-select",
|
||||
);
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1:alloy";
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1-f5:aria";
|
||||
|
||||
// If audio is already playing, pause it
|
||||
if (historicalUserAudio && !historicalUserAudio.paused) {
|
||||
|
|
@ -2331,7 +2334,7 @@ You have complete knowledge of this page content and can reference any details,
|
|||
const modalVoiceSelect = document.getElementById(
|
||||
"hermes-voice-select",
|
||||
);
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1:alloy";
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1-f5:aria";
|
||||
|
||||
// If audio is already playing, pause it
|
||||
if (historicalAudio && !historicalAudio.paused) {
|
||||
|
|
@ -2800,7 +2803,7 @@ You have complete knowledge of this page content and can reference any details,
|
|||
const modalVoiceSelect = document.getElementById(
|
||||
"hermes-voice-select",
|
||||
);
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1:alloy";
|
||||
const selectedVoice = modalVoiceSelect?.value || "tts-1-f5:aria";
|
||||
|
||||
// If audio is already playing, pause it
|
||||
if (introAudio && !introAudio.paused) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue