changeset 31:1e7d855afde3

voices
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 03 Aug 2025 17:05:38 -0600
parents d48f48e1b790
children d34d709a7a8e
files src/chat.html.luan src/chat.js src/lib/Chat.luan src/lib/Shared.luan src/save_chat.js.luan src/site.js src/tts.mp3.luan
diffstat 7 files changed, 46 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
diff -r d48f48e1b790 -r 1e7d855afde3 src/chat.html.luan
--- a/src/chat.html.luan	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/chat.html.luan	Sun Aug 03 17:05:38 2025 -0600
@@ -1,5 +1,6 @@
 local Luan = require "luan:Luan.luan"
 local error = Luan.error
+local ipairs = Luan.ipairs or error()
 local Parsers = require "luan:Parsers.luan"
 local json_string = Parsers.json_string or error()
 local Io = require "luan:Io.luan"
@@ -8,6 +9,7 @@
 local head = Shared.head or error()
 local header = Shared.header or error()
 local started = Shared.started or error()
+local voices = Shared.voices or error()
 local User = require "site:/lib/User.luan"
 local current_user = User.current_required or error()
 local Chat = require "site:/lib/Chat.luan"
@@ -67,6 +69,14 @@
 					<input name=name required><br>
 					<span error></span>
 				</p>
+				<p>
+					<label>Voice</label><br> 
+					<select name=voice>
+<%	for _, voice in ipairs(voices) do %>
+						<option value="<%=voice.code%>"><%=voice.name%></option>
+<%	end %>
+					<select>
+				</p>
 				<div buttons>
 					<button type=button onclick="closeModal(this)">Cancel</button>
 					<button type=submit>Save</button>
diff -r d48f48e1b790 -r 1e7d855afde3 src/chat.js
--- a/src/chat.js	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/chat.js	Sun Aug 03 17:05:38 2025 -0600
@@ -3,13 +3,22 @@
 let chat;
 
 function setChat(newChat) {
+	let audioChanged = chat && chat.voice != newChat.voice;
 	chat = newChat;
 	document.querySelector('[content] [name]').textContent = chat.name;
+	if(audioChanged) {
+		let s = `voice=${chat.voice}&`;
+		let audios = document.querySelectorAll('audio[src]');
+		for( let audio of audios ) {
+			audio.src = audio.src.replace(/voice=[^&]+&/,s);
+		}
+	}
 }
 
 function editChat(name) {
 	let dialog = document.querySelector('dialog[edit]');
 	dialog.querySelector('input[name=name]').value = chat.name;
+	dialog.querySelector('select[name=voice]').value = chat.voice;
 	dialog.showModal();
 }
 
@@ -50,7 +59,7 @@
 }
 
 function handleChatMarkdown() {
-	handleMarkdown(chat.language_region);
+	handleMarkdown(chat.language_region,chat.voice);
 }
 
 function scrollToEnd() {
@@ -83,7 +92,7 @@
 	textarea.parentNode.scrollIntoViewIfNeeded(false);
 	if( !audio )
 		audio = document.querySelector('div[buttons] audio');
-	audio.src = `/tts.mp3?lang=${chat.language_region}&text=${encodeURIComponent(textarea.value)}`;
+	audio.src = `/tts.mp3?lang=${chat.language_region}&voice=${chat.voice}&text=${encodeURIComponent(textarea.value)}`;
 }
 
 function askAi() {
diff -r d48f48e1b790 -r 1e7d855afde3 src/lib/Chat.luan
--- a/src/lib/Chat.luan	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/lib/Chat.luan	Sun Aug 03 17:05:38 2025 -0600
@@ -15,6 +15,8 @@
 local get_first = Utils.get_first or error()
 local Course = require "site:/lib/Course.luan"
 local get_course_by_id = Course.get_by_id or error()
+local Shared = require "site:/lib/Shared.luan"
+local voices = Shared.voices or error()
 
 
 local Chat = {}
@@ -30,6 +32,7 @@
 		ai_thread = doc.ai_thread
 		language = doc.language
 		language_region = doc.language_region
+		voice = doc.voice
 	}
 end
 
@@ -44,6 +47,7 @@
 		ai_thread = chat.ai_thread
 		language = chat.language or error()
 		language_region = chat.language_region or error()
+		voice = chat.voice or error()
 	}
 end
 
@@ -54,6 +58,7 @@
 function Chat.new(chat)
 	chat.updated = chat.updated or time_now()
 	chat.language_region = chat.language_region or first_region(chat.language)
+	chat.voice = chat.voice or voices[1].code
 
 	function chat.save()
 		local doc = to_doc(chat)
@@ -73,6 +78,7 @@
 		return {
 			id = chat.id
 			language_region = chat.language_region
+			voice = chat.voice
 			name = chat.name
 		}
 	end
diff -r d48f48e1b790 -r 1e7d855afde3 src/lib/Shared.luan
--- a/src/lib/Shared.luan	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/lib/Shared.luan	Sun Aug 03 17:05:38 2025 -0600
@@ -62,4 +62,15 @@
 	end )
 end
 
+Shared.voices = {
+	{
+		name = "Brandon"
+		code = "en-US-BrandonMultilingualNeural"
+	}
+	{
+		name = "Jenny"
+		code = "en-US-JennyMultilingualNeural"
+	}
+}
+
 return Shared
diff -r d48f48e1b790 -r 1e7d855afde3 src/save_chat.js.luan
--- a/src/save_chat.js.luan	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/save_chat.js.luan	Sun Aug 03 17:05:38 2025 -0600
@@ -15,10 +15,12 @@
 return function()
 	local chat = Http.request.parameters.chat or error()
 	local name = Http.request.parameters.name or error()
+	local voice = Http.request.parameters.voice or error()
 	run_in_transaction( function()
 		chat = get_chat_by_id(chat) or error()
 		chat.user_id == current_user().id or error()
 		chat.name = name
+		chat.voice = voice
 		chat.save()
 	end )
 	Io.stdout = Http.response.text_writer()
diff -r d48f48e1b790 -r 1e7d855afde3 src/site.js
--- a/src/site.js	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/site.js	Sun Aug 03 17:05:38 2025 -0600
@@ -124,7 +124,7 @@
 
 let mdDiv = document.createElement('div');
 
-function handleMarkdown(lang) {
+function handleMarkdown(lang,voice) {
 	let converter = window.markdownit({html: true});
 	let divs = document.querySelectorAll('[markdown]');
 	for( let div of divs ) {
@@ -138,7 +138,7 @@
 				rt.remove();
 			}
 			//console.log(mdDiv.textContent);
-			text += `\n<p><audio controls preload=none src="/tts.mp3?lang=${lang}&text=${encodeURIComponent(mdDiv.textContent)}"></audio></p>\n`;
+			text += `\n<p><audio controls preload=none src="/tts.mp3?lang=${lang}&voice=${voice}&text=${encodeURIComponent(mdDiv.textContent)}"></audio></p>\n`;
 		}
 		div.innerHTML = text;
 		div.removeAttribute('markdown');
diff -r d48f48e1b790 -r 1e7d855afde3 src/tts.mp3.luan
--- a/src/tts.mp3.luan	Sun Aug 03 12:25:01 2025 -0600
+++ b/src/tts.mp3.luan	Sun Aug 03 17:05:38 2025 -0600
@@ -18,10 +18,10 @@
 	["X-Microsoft-OutputFormat"] = "audio-16khz-128kbitrate-mono-mp3"
 }
 
-local function text_to_speech(lang,text)
+local function text_to_speech(lang,voice,text)
 	local xml = `%>
 <speak version='1.0' xml:lang='<%=lang%>'>
-    <voice name='en-US-BrandonMultilingualNeural'>
+    <voice name='<%=voice%>'>
 <%=		xml_encode(text) %>
     </voice>
 </speak>
@@ -36,7 +36,8 @@
 
 return function()
 	local lang = Http.request.parameters.lang or error()
+	local voice = Http.request.parameters.voice or error()
 	local text = Http.request.parameters.text or error()
-	local input = text_to_speech(lang,text)
+	local input = text_to_speech(lang,voice,text)
 	Http.response.binary_writer().write_from(input)
 end