feat: optimize TTS with direct processing for modal and Hermes for Read Page

- TTS modal now uses speakTextDirect() with simple JavaScript text cleaning
- No more Hermes preprocessing for modal text (faster, simpler)
- Read Page button still uses speakText() with Hermes for article extraction
- Reduced textarea height from 240px to 100px with vertical resize
- Added cleanTextForTTS() to remove markdown, HTML, and excess whitespace
- Modal gets instant TTS while Read Page gets intelligent content extraction
This commit is contained in:
Russell Ballestrini 2025-07-03 20:20:59 -04:00
parent 78ab413e8e
commit 17925537a6
2 changed files with 75 additions and 27 deletions

View file

@ -122,18 +122,22 @@ export function openTTSModal() {
if (USE_CUSTOM_STYLING) {
textArea.style.cssText = `
width: 100%;
height: 240px;
height: 100px;
resize: vertical;
min-height: 60px;
`;
} else {
textArea.style.cssText = `
width: 100%;
height: 240px;
height: 100px;
background: rgba(255,255,255,0.1);
color: inherit;
border: 1px solid rgba(128,128,128,0.3);
border-radius: 8px;
backdrop-filter: blur(10px);
padding: 0.5rem;
resize: vertical;
min-height: 60px;
`;
}
textArea.placeholder = getUIText("ttsPlaceholder");

View file

@ -2,10 +2,30 @@
import { API_KEY, MODEL, setLastTTS, TTS_API_URL } from "./config.js";
import { getSelectedModel, getSelectedModelEndpoint } from "./models.js";
// Function to read text using TTS
// Simple JavaScript text cleaning for TTS
function cleanTextForTTS(text) {
return text
// Remove markdown formatting
.replace(/\*\*([^*]+)\*\*/g, '$1') // Bold
.replace(/\*([^*]+)\*/g, '$1') // Italic
.replace(/__([^_]+)__/g, '$1') // Bold alt
.replace(/_([^_]+)_/g, '$1') // Italic alt
.replace(/#+\s/g, '') // Headers
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Links
.replace(/`([^`]+)`/g, '$1') // Inline code
.replace(/```[^`]*```/g, '') // Code blocks
// Remove HTML tags
.replace(/<[^>]+>/g, '')
// Remove excess whitespace
.replace(/\n\n+/g, '\n\n')
.replace(/[ \t]+/g, ' ')
.trim();
}
// Function to read text using TTS - for Read Page button (uses Hermes)
export async function speakText(text, voice = "alloy", rate = 0.9) {
try {
// Preprocess the text using the new function to get spoken tokens
// Preprocess the text using Hermes to get spoken tokens
const spokenText = await extractSpokenTokens(text);
const response = await fetch(TTS_API_URL, {
@ -40,6 +60,46 @@ export async function speakText(text, voice = "alloy", rate = 0.9) {
}
}
// Direct TTS function for modal - no Hermes preprocessing
export async function speakTextDirect(text, voice = "alloy", rate = 0.9) {
try {
// Clean text with simple JavaScript instead of Hermes
const cleanedText = cleanTextForTTS(text);
console.log("TTS input preview:", cleanedText.substring(0, 100) + "...");
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: cleanedText,
}),
});
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, blobUrl: audioUrl };
} catch (error) {
console.error("Error in TTS:", error);
throw error;
}
}
// Process page content using Hermes
export async function processContentWithHermes(content) {
const payload = {
@ -105,41 +165,25 @@ Ready? Breath and then return a stream of tokens to be used in a TTS system.
}
}
// Extract spoken tokens using Hermes - for Read Page functionality
export async function extractSpokenTokens(content) {
// Basic content filtering
const filteredContent = content
.replace(/hawk\s*tau/gi, '[filtered]')
.replace(/spit\s*on\s*that/gi, '[filtered]')
.trim();
// Skip processing if content is inappropriate or too short
if (filteredContent.length < 10 || filteredContent.includes('[filtered]')) {
console.warn('Content filtered or too short for TTS processing');
return content; // Return original but won't be processed
}
const payload = {
model: MODEL,
model: getSelectedModel(),
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.`,
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: filteredContent,
content: content,
},
],
temperature: 0,
max_tokens: 76000,
};
// Reduce console spam - only log content preview for long text
const contentPreview = filteredContent.length > 100
? filteredContent.substring(0, 100) + "..."
: filteredContent;
console.log("Processing TTS for content:", contentPreview);
console.log("Processing page content for TTS...");
try {
const response = await fetch(
@ -211,10 +255,10 @@ export async function generateTitleForTTS(text) {
.substring(0, 50); // Limit length for filesystem compatibility
}
// Handle TTS from element (for modal usage)
// Handle TTS from element (for modal usage - uses direct TTS without Hermes)
export async function handleTTS(text, voice = "alloy", rate = 0.9) {
try {
const result = await speakText(text, voice, rate);
const result = await speakTextDirect(text, voice, rate);
return result;
} catch (error) {
console.error("Error in handleTTS:", error);