eureka, we have uber TTS, using it three ways!

* read page
* tts anything
* tts ai generated message

	modified:   uncloseai.js
This commit is contained in:
root 2024-11-10 17:43:33 +00:00
parent 62674dfc5f
commit 8debf2b8bf

View file

@ -1,11 +1,3 @@
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.8.0/es/highlight.min.js';
// Configuration
const API_URL = "https://hermes.ai.unturf.com/v1/chat/completions";
const API_KEY = "dummy-api-key";
const MODEL = "NousResearch/Hermes-3-Llama-3.1-8B";
/* how to use in HTML */
/*
@ -23,6 +15,15 @@ const MODEL = "NousResearch/Hermes-3-Llama-3.1-8B";
</div>
*/
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.8.0/es/highlight.min.js';
// Configuration
const API_URL = "https://hermes.ai.unturf.com/v1/chat/completions";
const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech";
const API_KEY = "dummy-api-key";
const MODEL = "NousResearch/Hermes-3-Llama-3.1-8B";
// Initialize chat history
let chatHistory = [
{
@ -31,10 +32,9 @@ let chatHistory = [
}
];
// Function to extract text content from the webpage
//function extractWebpageContent() {
// return document.body.innerText;
//}
// Cache for TTS Anything
let lastTTSInput = '';
let lastTTSResult = null;
// Function to extract text content along with links and metadata from the webpage
function extractWebpageContent() {
@ -73,11 +73,318 @@ function extractWebpageContent() {
}
}
getTextWithLinks(document.body); // Start with the body element
getTextWithLinks(document.body);
return content.trim();
}
// Function to compute SHA-256 hash
async function sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// Function to open IndexedDB
function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('TTSCache', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore('audio', { keyPath: 'hash' });
};
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// Function to get audio from IndexedDB
async function getAudioFromCache(hash) {
const db = await openDatabase();
return new Promise((resolve, reject) => {
const transaction = db.transaction('audio', 'readonly');
const store = transaction.objectStore('audio');
const request = store.get(hash);
request.onsuccess = (event) => {
resolve(event.target.result ? event.target.result.audioData : null);
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// Function to save audio to IndexedDB
async function saveAudioToCache(hash, audioData) {
const db = await openDatabase();
return new Promise((resolve, reject) => {
const transaction = db.transaction('audio', 'readwrite');
const store = transaction.objectStore('audio');
const request = store.put({ hash, audioData });
request.onsuccess = () => {
resolve();
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
// Function to convert Blob to Base64
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// Function to convert Base64 to Blob
function base64ToBlob(base64) {
const byteString = atob(base64.split(',')[1]);
const mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ab], { type: mimeString });
}
// Function to read text using TTS
async function speakText(text, voice = 'alloy') {
const textHash = await sha256(text); // Use text for hashing
const cachedAudioData = await getAudioFromCache(textHash);
if (cachedAudioData) {
const audioBlob = base64ToBlob(cachedAudioData);
const audioUrl = URL.createObjectURL(audioBlob);
return { audio: new Audio(audioUrl), blob: audioBlob };
}
try {
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: text
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const audioBlob = await response.blob();
const audioData = await blobToBase64(audioBlob);
await saveAudioToCache(textHash, audioData); // Cache the audio data
const audioUrl = URL.createObjectURL(audioBlob);
return { audio: new Audio(audioUrl), blob: audioBlob };
} catch (error) {
console.error('Error in TTS:', error);
throw error;
}
}
// New function to process content using Hermes
async function processContentWithHermes(content) {
const payload = {
model: MODEL,
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.
Only summarize pre or code tags containing code.
How to summarize code: In place of any pre or code tags that have code, summarize the code instead of reading the code.
Here is a sample to show how code could be explained using English:
"a code block that shows how to use curl to post a json prompt to the nous research hermes 3 llama 3.1 8b model using a temperature of .5 and asking if the ai to 'Give a Python Fizzbuzz solution in one line of code'"
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.
Important remember do not summarize my words, only summarize code!
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: 14000
};
console.log("Sending payload to Hermes:", JSON.stringify(payload, null, 2));
try {
const response = await fetch(API_URL, {
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;
}
}
// Function to generate a title for TTS
async function generateTitleForTTS(text) {
const response = await fetch(API_URL, {
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();
}
// Function to read the entire page using Hermes
async function readPageWithHermes() {
const content = extractWebpageContent();
// Create status indicator
const statusDiv = document.createElement('div');
statusDiv.style.position = 'fixed';
statusDiv.style.top = '10px';
statusDiv.style.right = '10px';
statusDiv.style.padding = '10px';
statusDiv.style.background = 'rgba(0,0,0,0.8)';
statusDiv.style.color = 'white';
statusDiv.style.borderRadius = '5px';
statusDiv.style.zIndex = '1000';
document.body.appendChild(statusDiv);
statusDiv.textContent = 'Processing content with Hermes...';
const processedContent = await processContentWithHermes(content);
// Update status indicator
statusDiv.textContent = 'Converting text to speech...';
// Add pause/resume button
const pauseButton = document.createElement('button');
pauseButton.textContent = 'Pause Reading';
pauseButton.style.marginLeft = '10px';
statusDiv.appendChild(pauseButton);
let isPaused = false;
let currentAudio = null;
let currentBlob = null;
pauseButton.onclick = () => {
if (currentAudio) {
if (isPaused) {
currentAudio.play();
pauseButton.textContent = 'Pause Reading';
} else {
currentAudio.pause();
pauseButton.textContent = 'Resume Reading';
}
isPaused = !isPaused;
}
};
const { audio, blob } = await speakText(processedContent);
currentAudio = audio;
currentBlob = blob;
statusDiv.textContent = 'Reading page...';
statusDiv.appendChild(pauseButton);
// Add download button
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download MP3';
downloadButton.onclick = () => {
const a = document.createElement('a');
a.href = URL.createObjectURL(currentBlob);
a.download = `${document.title.replace(/\s+/g, '-').toLowerCase()}.mp3`;
a.click();
};
statusDiv.appendChild(downloadButton);
// Ensure the audio only plays once
audio.loop = false;
// Play the audio
await new Promise((resolve) => {
audio.onended = () => {
statusDiv.remove();
resolve();
};
audio.play();
});
}
// Generator function to send a message to the LLM and yield responses
async function* sendMessage(message) {
chatHistory.push({ role: "user", content: message });
@ -138,12 +445,10 @@ async function handleUserInput() {
const chatBox = document.getElementById('chat-box');
chatBox.innerHTML += `<p><strong>You:</strong> ${userInput}</p>`;
// Create a new paragraph for the AI response
const aiResponseParagraph = document.createElement('p');
aiResponseParagraph.innerHTML = '<strong>AI:</strong> ';
chatBox.appendChild(aiResponseParagraph);
// Create a span element for the actual response content
const responseContent = document.createElement('span');
aiResponseParagraph.appendChild(responseContent);
@ -153,16 +458,159 @@ async function handleUserInput() {
const parsedChunk = marked.parse(accumulatedContent);
responseContent.innerHTML = parsedChunk;
// Apply syntax highlighting to code blocks
responseContent.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
}
chatBox.scrollTop = chatBox.scrollHeight;
// Add play/pause button for TTS
const playPauseButton = document.createElement('button');
playPauseButton.textContent = 'Generate TTS for AI Response';
let aiAudio = null;
let aiBlob = null;
let isPaused = false;
playPauseButton.onclick = async () => {
if (!aiAudio) {
playPauseButton.textContent = 'Processing...';
playPauseButton.disabled = true; // Disable button while processing
const result = await speakText(accumulatedContent);
aiAudio = result.audio;
aiBlob = result.blob;
playPauseButton.textContent = 'Pause AI Response';
playPauseButton.disabled = false; // Re-enable button after processing
aiAudio.play();
// Generate title for the MP3 file
const title = await generateTitleForTTS(accumulatedContent);
// Add download button
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download MP3';
downloadButton.onclick = () => {
const a = document.createElement('a');
a.href = URL.createObjectURL(aiBlob);
a.download = `${title}.mp3`;
a.click();
};
chatBox.appendChild(downloadButton);
} else {
if (isPaused) {
aiAudio.play();
playPauseButton.textContent = 'Pause AI Response';
} else {
aiAudio.pause();
playPauseButton.textContent = 'Play AI Response';
}
isPaused = !isPaused;
}
};
chatBox.appendChild(playPauseButton);
}
// Function to initialize the chat interface
// Function to open TTS modal
async function openTTSModal() {
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.backgroundColor = 'rgba(0,0,0,0.5)';
modal.style.display = 'flex';
modal.style.justifyContent = 'center';
modal.style.alignItems = 'center';
modal.style.zIndex = '1001';
const modalContent = document.createElement('div');
modalContent.style.backgroundColor = 'white';
modalContent.style.padding = '20px';
modalContent.style.borderRadius = '5px';
modalContent.style.position = 'relative';
modal.appendChild(modalContent);
const closeButton = document.createElement('button');
closeButton.textContent = 'X';
closeButton.style.position = 'absolute';
closeButton.style.top = '10px';
closeButton.style.right = '10px';
closeButton.onclick = () => document.body.removeChild(modal);
modalContent.appendChild(closeButton);
const textArea = document.createElement('textarea');
textArea.style.width = '100%';
textArea.style.height = '100px';
modalContent.appendChild(textArea);
const playButton = document.createElement('button');
playButton.textContent = 'Play Text';
playButton.onclick = async () => {
const currentText = textArea.value.trim();
if (currentText !== lastTTSInput) {
playButton.textContent = 'Processing...';
playButton.disabled = true; // Disable button while processing
lastTTSInput = currentText;
lastTTSResult = await speakText(currentText);
playButton.textContent = 'Pause Text';
playButton.disabled = false; // Re-enable button after processing
// Generate title for the MP3 file
const title = await generateTitleForTTS(currentText);
// Remove existing download button if present
const existingDownloadButton = modalContent.querySelector('button.download');
if (existingDownloadButton) {
modalContent.removeChild(existingDownloadButton);
}
const downloadButton = document.createElement('button');
downloadButton.textContent = 'Download MP3';
downloadButton.className = 'download';
downloadButton.onclick = () => {
const a = document.createElement('a');
a.href = URL.createObjectURL(lastTTSResult.blob);
a.download = `${title}.mp3`;
a.click();
};
modalContent.appendChild(downloadButton);
}
if (lastTTSResult) {
if (lastTTSResult.audio.paused) {
lastTTSResult.audio.play();
playButton.textContent = 'Pause Text';
} else {
lastTTSResult.audio.pause();
playButton.textContent = 'Play Text';
}
}
};
modalContent.appendChild(playButton);
document.body.appendChild(modal);
}
// Add button to page
function addReadPageButton() {
const button = document.createElement('button');
button.textContent = 'Read Page';
button.onclick = readPageWithHermes;
button.style.margin = '10px';
const ttsButton = document.createElement('button');
ttsButton.textContent = 'TTS Anything';
ttsButton.onclick = openTTSModal;
ttsButton.style.margin = '10px';
// Insert before the chat container
const chatContainer = document.getElementById('chat-container');
chatContainer.parentNode.insertBefore(button, chatContainer);
chatContainer.parentNode.insertBefore(ttsButton, chatContainer);
}
// Initialize the chat interface
function initializeChatInterface() {
const pageContent = extractWebpageContent();
chatHistory.push({
@ -170,15 +618,18 @@ function initializeChatInterface() {
content: `Here's the content of the webpage: ${pageContent}`
});
// Set up marked.js options
marked.setOptions({
highlight: function(code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
}
});
// Add the Read Page button
addReadPageButton();
}
// Event listeners
document.getElementById("user-input").addEventListener("keydown", function(event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
@ -186,8 +637,9 @@ document.getElementById("user-input").addEventListener("keydown", function(event
}
});
// Initialize the chat interface when the page loads
// Initialize on page load
window.onload = initializeChatInterface;
// Expose handleUserInput to the global scope
// Export functions to global scope
window.handleUserInput = handleUserInput;
window.readPage = readPageWithHermes;