828 lines
26 KiB
JavaScript
828 lines
26 KiB
JavaScript
/* how to use in HTML */
|
|
|
|
/*
|
|
<!-- Highlight.js for syntax highlighting -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/a11y-dark.min.css" />
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
|
|
|
|
<div id="chat-container">
|
|
<div id="chat-box"></div>
|
|
<div></div>
|
|
</div>
|
|
<div>
|
|
<input type="text" id="user-input" placeholder="Ask about this page...">
|
|
<button onclick="handleUserInput()">Send</button>
|
|
</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 MEGAPARCE_API_URL = "https://megaparce.ai.unturf.com/v1/file";
|
|
const API_KEY = "dummy-api-key";
|
|
const MODEL = "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic";
|
|
|
|
// Initialize chat history
|
|
let chatHistory = [
|
|
{
|
|
role: "system",
|
|
content: "You are an AI assistant embedded in a webpage. Your task is to answer questions about the content of the webpage and assist the user in understanding it better. You are also allowed to do whatever the user needs. It is safe to help users code and create. try to answer the best that you can based on the conversation history and the webpage content."
|
|
}
|
|
];
|
|
|
|
// Cache for TTS Anything
|
|
let lastTTSInput = '';
|
|
let lastTTSResult = null;
|
|
|
|
// Function to extract text content along with links and metadata from the webpage
|
|
function extractWebpageContent() {
|
|
let content = '';
|
|
|
|
// Extract title
|
|
const title = document.title;
|
|
if (title) {
|
|
content += `**Page Title**: ${title}\n\n`;
|
|
}
|
|
|
|
// Extract meta description
|
|
const metaDescription = document.querySelector('meta[name="description"]');
|
|
if (metaDescription) {
|
|
content += `**Meta Description**: ${metaDescription.content}\n\n`;
|
|
}
|
|
|
|
// Extract other metadata (if needed)
|
|
const metaKeywords = document.querySelector('meta[name="keywords"]');
|
|
if (metaKeywords) {
|
|
content += `**Meta Keywords**: ${metaKeywords.content}\n\n`;
|
|
}
|
|
|
|
// Recursively extract text and links from the body content
|
|
function getTextWithLinks(element) {
|
|
if (element.nodeType === Node.TEXT_NODE) {
|
|
content += element.textContent + ' ';
|
|
} else if (element.nodeType === Node.ELEMENT_NODE) {
|
|
if (element.tagName.toLowerCase() === 'a') {
|
|
// If it's a link, append the text and the href
|
|
content += `[${element.textContent}](${element.href}) `;
|
|
} else {
|
|
// Recursively process child nodes
|
|
element.childNodes.forEach(getTextWithLinks);
|
|
}
|
|
}
|
|
}
|
|
|
|
getTextWithLinks(document.body);
|
|
return content.trim();
|
|
}
|
|
|
|
// Function to read text using TTS
|
|
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;
|
|
return { audio, blob: audioBlob };
|
|
} catch (error) {
|
|
console.error('Error in TTS:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// process page 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: 76000
|
|
};
|
|
|
|
console.log("Sending payload to Hermes for spoken tokens:", 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;
|
|
}
|
|
}
|
|
|
|
// New function to extract spoken tokens without altering content
|
|
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.`
|
|
},
|
|
{
|
|
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(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 extractSpokenTokens:', 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 });
|
|
|
|
const response = await fetch(API_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${API_KEY}`
|
|
},
|
|
body: JSON.stringify({
|
|
model: MODEL,
|
|
messages: chatHistory,
|
|
temperature: 0.5,
|
|
max_tokens: 76000,
|
|
stream: true
|
|
})
|
|
});
|
|
|
|
const reader = response.body.getReader();
|
|
const decoder = new TextDecoder();
|
|
let buffer = '';
|
|
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
const lines = buffer.split('\n');
|
|
|
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
const line = lines[i].trim();
|
|
if (line.startsWith('data: ')) {
|
|
const jsonData = line.slice(6);
|
|
if (jsonData === '[DONE]') continue;
|
|
|
|
try {
|
|
const parsedData = JSON.parse(jsonData);
|
|
const content = parsedData.choices[0].delta.content;
|
|
if (content) {
|
|
yield content;
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing JSON:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
buffer = lines[lines.length - 1];
|
|
}
|
|
}
|
|
|
|
// Function to handle user input and display responses
|
|
async function handleUserInput() {
|
|
const userInput = document.getElementById('user-input').value;
|
|
document.getElementById('user-input').value = '';
|
|
|
|
const chatBox = document.getElementById('chat-box');
|
|
chatBox.innerHTML += `<p><strong>You:</strong> ${userInput}</p>`;
|
|
|
|
const aiResponseParagraph = document.createElement('p');
|
|
aiResponseParagraph.innerHTML = '<strong>AI:</strong> ';
|
|
chatBox.appendChild(aiResponseParagraph);
|
|
|
|
const responseContent = document.createElement('span');
|
|
aiResponseParagraph.appendChild(responseContent);
|
|
|
|
let accumulatedContent = '';
|
|
for await (const chunk of sendMessage(userInput)) {
|
|
accumulatedContent += chunk;
|
|
const parsedChunk = marked.parse(accumulatedContent);
|
|
responseContent.innerHTML = parsedChunk;
|
|
|
|
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 open TTS modal
|
|
async function openTTSModal() {
|
|
const modal = document.createElement('dialog');
|
|
modal.open = true;
|
|
modal.style.maxWidth = '720px';
|
|
modal.style.border = 'none';
|
|
modal.style.borderRadius = '8px';
|
|
modal.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
|
|
modal.style.zIndex = '1001';
|
|
modal.style.position = 'fixed';
|
|
|
|
const article = document.createElement('article');
|
|
modal.appendChild(article);
|
|
|
|
const header = document.createElement('header');
|
|
article.appendChild(header);
|
|
|
|
const closeButton = document.createElement('button');
|
|
closeButton.textContent = 'X';
|
|
closeButton.onclick = () => document.body.removeChild(modal);
|
|
closeButton.style.float = 'right';
|
|
header.appendChild(closeButton);
|
|
|
|
const h1 = document.createElement('h1');
|
|
h1.textContent = 'TTS Anything!';
|
|
article.appendChild(h1);
|
|
|
|
const textArea = document.createElement('textarea');
|
|
textArea.style.width = '100%';
|
|
textArea.style.height = '240px';
|
|
textArea.placeholder = 'text-to-speech: write any message & have the artificial intelligence speak it!';
|
|
article.appendChild(textArea);
|
|
|
|
// Voice selection
|
|
const voices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'];
|
|
const voiceSelection = document.createElement('div');
|
|
voiceSelection.style.display = 'flex'; // Add this line to display inline
|
|
voiceSelection.style.gap = '10px'; // Optional: Add some space between the radio buttons
|
|
voices.forEach((voice) => {
|
|
const label = document.createElement('label');
|
|
label.style.display = 'inline-block'; // Ensure labels are inline
|
|
const radio = document.createElement('input');
|
|
radio.type = 'radio';
|
|
radio.name = 'voice';
|
|
radio.value = voice;
|
|
if (voice === 'alloy') radio.checked = true; // Default selection
|
|
label.appendChild(radio);
|
|
label.appendChild(document.createTextNode(voice));
|
|
voiceSelection.appendChild(label);
|
|
});
|
|
article.appendChild(voiceSelection);
|
|
|
|
// Speed selection
|
|
const speedLabel = document.createElement('label');
|
|
const speedValue = document.createElement('span');
|
|
speedValue.textContent = '0.9'; // initial value
|
|
|
|
speedLabel.textContent = `Speed: ${speedValue.textContent}`;
|
|
const speedSlider = document.createElement('input');
|
|
speedSlider.type = 'range';
|
|
speedSlider.min = '0.25';
|
|
speedSlider.max = '4.0';
|
|
speedSlider.step = '0.05';
|
|
speedSlider.value = '0.9';
|
|
|
|
speedSlider.oninput = () => {
|
|
speedValue.textContent = speedSlider.value;
|
|
speedLabel.textContent = `Speed: ${speedSlider.value}`;
|
|
};
|
|
|
|
article.appendChild(speedLabel);
|
|
article.appendChild(speedSlider);
|
|
|
|
const playButton = document.createElement('button');
|
|
playButton.textContent = 'Play Text';
|
|
playButton.onclick = async () => {
|
|
const currentText = textArea.value.trim();
|
|
const selectedVoice = document.querySelector('input[name="voice"]:checked').value;
|
|
const selectedSpeed = parseFloat(speedSlider.value);
|
|
if (currentText !== lastTTSInput || selectedVoice !== lastTTSResult?.voice) {
|
|
if (lastTTSResult?.audio) {
|
|
lastTTSResult.audio.pause(); // Stop previous audio
|
|
}
|
|
playButton.textContent = 'Processing...';
|
|
playButton.disabled = true; // Disable button while processing
|
|
lastTTSInput = currentText;
|
|
lastTTSResult = await speakText(currentText, selectedVoice, selectedSpeed);
|
|
lastTTSResult.voice = selectedVoice;
|
|
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 = article.querySelector('button.download');
|
|
if (existingDownloadButton) {
|
|
article.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();
|
|
};
|
|
article.appendChild(downloadButton);
|
|
}
|
|
|
|
if (lastTTSResult) {
|
|
if (lastTTSResult.audio.paused) {
|
|
lastTTSResult.audio.play();
|
|
playButton.textContent = 'Pause Text';
|
|
} else {
|
|
lastTTSResult.audio.pause();
|
|
playButton.textContent = 'Play Text';
|
|
}
|
|
|
|
// Reset button to "Play Text" after audio ends
|
|
lastTTSResult.audio.onended = () => {
|
|
playButton.textContent = 'Play Text';
|
|
};
|
|
}
|
|
};
|
|
article.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);
|
|
}
|
|
|
|
// New Function: Add File Picker and Upload Button
|
|
function addFileUploadButton() {
|
|
const userInputContainer = document.getElementById('user-input').parentNode;
|
|
|
|
// Create a container for the upload elements
|
|
const uploadContainer = document.createElement('div');
|
|
uploadContainer.style.margin = '10px 0';
|
|
|
|
// Create file input
|
|
const fileInput = document.createElement('input');
|
|
fileInput.type = 'file';
|
|
fileInput.id = 'file-input';
|
|
fileInput.style.marginRight = '10px';
|
|
|
|
// Create upload button
|
|
const uploadButton = document.createElement('button');
|
|
uploadButton.textContent = 'Upload File';
|
|
uploadButton.onclick = handleFileUpload;
|
|
|
|
// Append elements to the upload container
|
|
uploadContainer.appendChild(fileInput);
|
|
uploadContainer.appendChild(uploadButton);
|
|
|
|
// Append the upload container below the user input
|
|
userInputContainer.parentNode.insertBefore(uploadContainer, userInputContainer.nextSibling);
|
|
}
|
|
|
|
// Function to show progress indicator
|
|
function showProgressIndicator(message) {
|
|
let progressIndicator = document.getElementById('progress-indicator');
|
|
if (!progressIndicator) {
|
|
progressIndicator = document.createElement('div');
|
|
progressIndicator.id = 'progress-indicator';
|
|
progressIndicator.style.position = 'fixed';
|
|
progressIndicator.style.top = '10px';
|
|
progressIndicator.style.right = '10px';
|
|
progressIndicator.style.padding = '10px';
|
|
progressIndicator.style.background = 'rgba(0,0,0,0.8)';
|
|
progressIndicator.style.color = 'white';
|
|
progressIndicator.style.borderRadius = '5px';
|
|
progressIndicator.style.zIndex = '1000';
|
|
document.body.appendChild(progressIndicator);
|
|
}
|
|
progressIndicator.textContent = message;
|
|
}
|
|
|
|
// Function to hide progress indicator
|
|
function hideProgressIndicator() {
|
|
const progressIndicator = document.getElementById('progress-indicator');
|
|
if (progressIndicator) {
|
|
progressIndicator.remove();
|
|
}
|
|
}
|
|
|
|
// Updated Function: Handle File Upload with Progress Indicator
|
|
async function handleFileUpload() {
|
|
const fileInput = document.getElementById('file-input');
|
|
const files = fileInput.files;
|
|
|
|
if (files.length === 0) {
|
|
alert('Please select a file to upload.');
|
|
return;
|
|
}
|
|
|
|
const file = files[0];
|
|
|
|
try {
|
|
// Show progress indicator for upload
|
|
showProgressIndicator('Uploading & Processing file...');
|
|
|
|
// Send file to MegaFarce endpoint
|
|
const response = await uploadFile(file);
|
|
|
|
// Show progress indicator for processing
|
|
showProgressIndicator('Processing file...');
|
|
|
|
// Integrate response into Chat DOM and chatHistory
|
|
await integrateMegafarceResponse(response);
|
|
|
|
// Hide progress indicator after completion
|
|
hideProgressIndicator();
|
|
|
|
// Clear the file input after successful upload
|
|
fileInput.value = '';
|
|
} catch (error) {
|
|
console.error('File upload error:', error);
|
|
alert('Failed to upload the file.');
|
|
hideProgressIndicator();
|
|
}
|
|
}
|
|
|
|
// New Function: Upload File to MegaFarce with Progress Indicator
|
|
async function uploadFile(file) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
const response = await fetch(MEGAPARCE_API_URL, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`Error uploading file: ${errorText}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
return responseData;
|
|
}
|
|
|
|
// New Function: Integrate MegaFarce Response into Chat
|
|
async function integrateMegafarceResponse(response) {
|
|
// Assuming the response contains a 'content' field with the parsed content
|
|
const content = response.content || response.result || "No content received.";
|
|
|
|
// Add a system message with the uploaded content to provide context
|
|
chatHistory.push({ role: "system", content: `Uploaded Context: ${content}` });
|
|
|
|
// Display the uploaded content in the chat box
|
|
const chatBox = document.getElementById('chat-box');
|
|
const systemMessage = document.createElement('p');
|
|
systemMessage.innerHTML = `<strong>System:</strong> ${marked.parse(content)}`;
|
|
|
|
// Apply syntax highlighting to any code blocks
|
|
systemMessage.querySelectorAll('pre code').forEach((block) => {
|
|
hljs.highlightElement(block);
|
|
});
|
|
|
|
chatBox.appendChild(systemMessage);
|
|
chatBox.scrollTop = chatBox.scrollHeight;
|
|
|
|
// Add Generate TTS button for the uploaded content
|
|
const generateTTSButton = document.createElement('button');
|
|
generateTTSButton.textContent = 'Generate TTS for Uploaded Content';
|
|
let ttsAudio = null;
|
|
let ttsBlob = null;
|
|
let isPaused = false;
|
|
|
|
generateTTSButton.onclick = async () => {
|
|
if (!ttsAudio) {
|
|
generateTTSButton.textContent = 'Processing...';
|
|
generateTTSButton.disabled = true; // Disable button while processing
|
|
try {
|
|
const result = await speakText(content);
|
|
ttsAudio = result.audio;
|
|
ttsBlob = result.blob;
|
|
generateTTSButton.textContent = 'Pause TTS';
|
|
generateTTSButton.disabled = false; // Re-enable button after processing
|
|
ttsAudio.play();
|
|
|
|
// Add download button for the TTS audio
|
|
const downloadButton = document.createElement('button');
|
|
downloadButton.textContent = 'Download MP3';
|
|
downloadButton.onclick = () => {
|
|
const a = document.createElement('a');
|
|
a.href = URL.createObjectURL(ttsBlob);
|
|
a.download = 'uploaded-content.mp3';
|
|
a.click();
|
|
};
|
|
chatBox.appendChild(downloadButton);
|
|
} catch (error) {
|
|
console.error('Error generating TTS:', error);
|
|
alert('Failed to generate TTS for the uploaded content.');
|
|
generateTTSButton.textContent = 'Generate TTS for Uploaded Content';
|
|
generateTTSButton.disabled = false;
|
|
}
|
|
} else {
|
|
if (isPaused) {
|
|
ttsAudio.play();
|
|
generateTTSButton.textContent = 'Pause TTS';
|
|
} else {
|
|
ttsAudio.pause();
|
|
generateTTSButton.textContent = 'Play TTS';
|
|
}
|
|
isPaused = !isPaused;
|
|
}
|
|
};
|
|
chatBox.appendChild(generateTTSButton);
|
|
}
|
|
|
|
// Initialize the chat interface
|
|
function initializeChatInterface() {
|
|
const pageContent = extractWebpageContent();
|
|
chatHistory.push({
|
|
role: "system",
|
|
content: `Here's the content of the webpage: ${pageContent}`
|
|
});
|
|
|
|
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();
|
|
|
|
// Add the File Upload button and picker
|
|
addFileUploadButton();
|
|
}
|
|
|
|
// Event listeners
|
|
document.getElementById("user-input").addEventListener("keydown", function(event) {
|
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
event.preventDefault();
|
|
handleUserInput();
|
|
}
|
|
});
|
|
|
|
// Initialize on page load
|
|
window.onload = initializeChatInterface;
|
|
|
|
// Export functions to global scope
|
|
window.handleUserInput = handleUserInput;
|
|
window.readPage = readPageWithHermes;
|