* Update README.rst

* button grid vertical
* button TTS play button using the free speech.ai.unturf.com endpoint!

	modified:   README.rst
	modified:   templates/base.html
	modified:   templates/chat.html
This commit is contained in:
Russell 2024-11-11 07:55:02 -05:00 committed by Russell Ballestrini
parent 496dc42f13
commit 7f332de648
3 changed files with 146 additions and 14 deletions

View file

@ -3,7 +3,7 @@ Open Completion
* `opencompletion.com <https://opencompletion.com>`_
* `demo.opencompletion.com running vllm/hermes-llama-3 model <https://demo.opencompletion.com >`_
* `demo.opencompletion.com running vllm/hermes-llama-3 model <https://demo.opencompletion.com>`_
originally named: flask-socketio-llm-completions

View file

@ -28,7 +28,6 @@
const socket = io.connect(window.location.protocol + "//" + document.domain + ":" + location.port);
</script>
<!-- Link to the favicon -->
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
@ -83,20 +82,30 @@
/* Styling for individual message wrappers */
.message-wrapper {
display: flex;
display: grid;
grid-template-columns: auto 1fr;
align-items: start;
gap: 4px;
margin-bottom: 32px;
}
/* Styling for the delete and edit buttons next to messages */
.message-wrapper button {
margin-top: 16px;
margin-right: 8px;
margin-right: 4px;
margin-bottom: 4px;
}
/* Styling for the button container within each message */
.button-container {
display: grid;
grid-auto-rows: min-content; /* Ensure each button takes up only as much space as it needs */
gap: 4px; /* Vertical space between buttons */
}
/* Styling for paragraphs, used for messages */
p {
margin-top: 8px;
margin-bottom: 8px;
margin: 0;
margin-bottom: 12px;
}
/* Styling for the main container that holds the rooms list and chat */

View file

@ -19,7 +19,9 @@
</div>
<script>
// Retrieve username and room name from the URL parameters
// Constants
const API_KEY = "dummy-api-key";
const TTS_API_URL = "https://speech.ai.unturf.com/v1/audio/speech";
const urlParams = new URLSearchParams(window.location.search);
const username = urlParams.get("username");
const room_name = "{{ room_name }}";
@ -36,6 +38,8 @@ const dompurify_config = {
// keeping track of scrolling to prevent autoscrolling.
let userHasScrolledUp = false;
let currentAudio = null; // To keep track of the currently playing audio
let audioCache = {}; // Cache to store audio blobs
document.addEventListener('DOMContentLoaded', (event) => {
const chatContainer = document.getElementById("chat");
@ -95,6 +99,79 @@ socket.on("connect", () => {
socket.emit("join", {"username": username, "room_name": room_name});
});
// Function to read text using TTS
async function speakText(text, playButton, messageId, voice = 'onyx', rate = 0.9) {
try {
// Check if the audio is already cached
if (audioCache[messageId]) {
const audio = audioCache[messageId];
toggleAudioPlayback(audio, playButton);
return;
}
// Set button to processing state
playButton.textContent = "Processing...";
playButton.disabled = true;
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 audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.playbackRate = rate;
// Cache the audio
audioCache[messageId] = audio;
// Enable button and change text to "Pause"
playButton.disabled = false;
toggleAudioPlayback(audio, playButton);
} catch (error) {
console.error('Error in TTS:', error);
playButton.textContent = "Play"; // Reset button text on error
playButton.disabled = false;
}
}
// Function to toggle audio playback
function toggleAudioPlayback(audio, playButton) {
if (currentAudio && currentAudio !== audio) {
currentAudio.pause();
currentAudio.currentTime = 0;
currentAudio.playButton.textContent = "Play";
}
if (audio.paused) {
audio.play();
playButton.textContent = "Pause";
} else {
audio.pause();
playButton.textContent = "Play";
}
currentAudio = audio;
currentAudio.playButton = playButton;
audio.onended = () => {
playButton.textContent = "Play";
};
}
// Socket event for receiving a new message
socket.on("chat_message", (data) => {
const messageWrapper = document.createElement("div");
@ -120,18 +197,30 @@ socket.on("chat_message", (data) => {
if (data.id) {
newMessage.dataset.rawMarkdown = data.content;
// Create a container for the buttons
const buttonContainer = document.createElement("div");
buttonContainer.className = "button-container";
// Create the delete button
const deleteButton = document.createElement("button");
deleteButton.innerHTML = "x";
deleteButton.onclick = () => deleteMessage(data.id, room_name);
messageWrapper.appendChild(deleteButton);
buttonContainer.appendChild(deleteButton);
// Create the edit button
const editButton = document.createElement("button");
editButton.textContent = "Edit";
editButton.className = "edit-button";
editButton.onclick = () => editMessage(data.id, newMessage, data.content);
messageWrapper.appendChild(editButton);
buttonContainer.appendChild(editButton);
// Create the play button for TTS
const playButton = document.createElement("button");
playButton.textContent = "Play";
playButton.onclick = () => speakText(data.content, playButton, data.id);
buttonContainer.appendChild(playButton);
messageWrapper.appendChild(buttonContainer);
}
messageWrapper.appendChild(newMessage);
@ -178,19 +267,30 @@ socket.on("previous_messages", (data) => {
newMessage.dataset.rawMarkdown = data.content;
// Create a container for the buttons
const buttonContainer = document.createElement("div");
buttonContainer.className = "button-container";
// Create the delete button
const deleteButton = document.createElement("button");
deleteButton.innerHTML = "x";
deleteButton.onclick = () => deleteMessage(data.id, room_name);
messageWrapper.appendChild(deleteButton);
buttonContainer.appendChild(deleteButton);
// Create the edit button
const editButton = document.createElement("button");
editButton.textContent = "Edit";
editButton.className = "edit-button";
editButton.onclick = () => editMessage(data.id, newMessage);
messageWrapper.appendChild(editButton);
buttonContainer.appendChild(editButton);
// Create the play button for TTS
const playButton = document.createElement("button");
playButton.textContent = "Play";
playButton.onclick = () => speakText(data.content, playButton, data.id);
buttonContainer.appendChild(playButton);
messageWrapper.appendChild(buttonContainer);
messageWrapper.appendChild(newMessage);
document.getElementById("chat").appendChild(messageWrapper);
@ -238,19 +338,30 @@ socket.on("message_chunk", (data) => {
targetMessageElement = document.createElement("div");
targetMessageElement.className = "message-content";
// Create a container for the buttons
const buttonContainer = document.createElement("div");
buttonContainer.className = "button-container";
// Create the "x" button for deletion
const deleteButton = document.createElement("button");
deleteButton.innerHTML = "x";
deleteButton.onclick = () => deleteMessage(data.id, room_name);
messageWrapper.appendChild(deleteButton);
buttonContainer.appendChild(deleteButton);
// Create the edit button
const editButton = document.createElement("button");
editButton.textContent = "Edit";
editButton.className = "edit-button";
editButton.onclick = () => editMessage(data.id, targetMessageElement);
messageWrapper.appendChild(editButton);
buttonContainer.appendChild(editButton);
// Create the play button for TTS
const playButton = document.createElement("button");
playButton.textContent = "Play";
playButton.onclick = () => speakText(data.content, playButton, data.id);
buttonContainer.appendChild(playButton);
messageWrapper.appendChild(buttonContainer);
messageWrapper.appendChild(targetMessageElement);
} else {
// If the wrapper already exists, get the message-content div inside it
@ -373,12 +484,24 @@ function saveEditedMessage(messageId, textarea, messageContentContainer) {
"room_name": room_name
});
// Clear the cached audio for this message to recompute TTS
if (audioCache[messageId]) {
delete audioCache[messageId];
}
// Reset the edit button to its original state
const messageWrapper = messageContentContainer.closest('.message-wrapper');
const editButton = messageWrapper.querySelector(".edit-button");
editButton.textContent = 'Edit';
editButton.onclick = () => editMessage(messageId, messageContentContainer, updatedMarkdown);
// Reset the play button to its initial state
const playButton = Array.from(messageWrapper.querySelectorAll("button")).find(btn => btn.textContent === 'Pause' || btn.textContent === 'Play');
if (playButton) {
playButton.textContent = 'Play';
playButton.onclick = () => speakText(updatedMarkdown, playButton, messageId); // Ensure it uses the updated content
}
// Remove the save button using the 'save-button' class
const saveButton = messageWrapper.querySelector(".save-button");
if (saveButton) {