uncloseai.com/src/tts.js
Russell Ballestrini eee33986cf feat(ui): add comprehensive language localization system
- Add complete UI translations for all 19 supported languages
- Add language preference dropdown in modal settings
- Store language preference in localStorage
- Inject language preference into Hermes system prompts
- Add smart translation dropdown with AI-powered language detection
- Keep both original translation modal and new smart dropdown
- Remove non-functional upload button from modal
- Add biome.json config to ignore third-party CSS files
2025-07-03 11:09:10 -04:00

194 lines
4.8 KiB
JavaScript

// Text-to-speech functionality
import { API_KEY, MODEL, setLastTTS, TTS_API_URL } from "./config.js";
import { getSelectedModel, getSelectedModelEndpoint } from "./models.js";
// Function to read text using TTS
export async function speakText(text, voice = "alloy", rate = 0.9) {
try {
// Preprocess the text using the new function to get spoken tokens
const spokenText = await extractSpokenTokens(text);
const response = await fetch(TTS_API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: "tts-1",
voice: voice,
input: spokenText,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.playbackRate = rate;
// Store the last TTS result
setLastTTS(text, { audio, blob: audioBlob });
return { audio, blob: audioBlob };
} catch (error) {
console.error("Error in TTS:", error);
throw error;
}
}
// Process page content using Hermes
export async function processContentWithHermes(content) {
const payload = {
model: getSelectedModel(),
messages: [
{
role: "system",
content: `Extract the exact main content of the article.
Avoid reading ads.
Do not change or summarize content. Return the same words verbatim.
When you encounter a list or bullets, add . . . to make the TTS pause between items.
Try to stay as close to the truth of the original version as possible. Start with the title of the post and then jump into it.
Do not mention the TTS stream just do the work!
Remember your only goal is to extract the entire & exact main content of the article.
Ready? Breath and then return a stream of tokens to be used in a TTS system.
`,
},
{
role: "user",
content: content,
},
],
temperature: 0,
max_tokens: 76000,
};
console.log(
"Sending payload to LLM for spoken tokens:",
JSON.stringify(payload, null, 2),
);
try {
const response = await fetch(
`${getSelectedModelEndpoint()}/chat/completions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify(payload),
},
);
if (!response.ok) {
const errorText = await response.text();
console.error(`HTTP error! status: ${response.status}`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.choices[0].message.content;
} catch (error) {
console.error("Error in processContentWithHermes:", error);
throw error;
}
}
export async function extractSpokenTokens(content) {
const payload = {
model: MODEL,
messages: [
{
role: "system",
content: `Extract the spoken tokens from the content without altering or summarizing it. Ensure that the spoken tokens match the content displayed on the screen. Skip any embedded advertisements. Remove asterisks, markdown symbols, and other characters that should not be spoken, such as **, *, #, or []. Preserve the original text's meaning and structure.`,
},
{
role: "user",
content: content,
},
],
temperature: 0,
max_tokens: 76000,
};
console.log(
"Sending payload to Hermes for spoken tokens:",
JSON.stringify(payload, null, 2),
);
try {
const response = await fetch(
`${getSelectedModelEndpoint()}/chat/completions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify(payload),
},
);
if (!response.ok) {
const errorText = await response.text();
console.error(`HTTP error! status: ${response.status}`, errorText);
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.choices[0].message.content;
} catch (error) {
console.error("Error in extractSpokenTokens:", error);
throw error;
}
}
// Function to generate a title for TTS
export async function generateTitleForTTS(text) {
const response = await fetch(
`${getSelectedModelEndpoint()}/chat/completions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
model: MODEL,
messages: [
{
role: "system",
content: "Generate a concise title for the following text.",
},
{
role: "user",
content: text,
},
],
temperature: 0.5,
max_tokens: 60,
}),
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.choices[0].message.content
.trim()
.replace(/\s+/g, "-")
.toLowerCase();
}