Mercurial Hosting > lang
changeset 46:cc20eebaa74a
use openai tts
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 14 Aug 2025 11:27:34 +0900 |
parents | fef7a5c65cfb |
children | 3cd6f36c81d4 |
files | courses/j1.txt src/chat.html.luan src/chat.js src/chats.html.luan src/edit_course.html.luan src/lang_courses.html.luan src/lib/Chat.luan src/lib/Course.luan src/lib/Shared.luan src/lib/Utils.luan src/lib/languages.luan src/new_chat.red.luan src/private/Config_sample.luan src/private/tools/chat.html.luan src/save_chat.js.luan src/save_course.js.luan src/site.js src/tts.mp3.luan src/your_courses.html.luan |
diffstat | 19 files changed, 75 insertions(+), 116 deletions(-) [+] |
line wrap: on
line diff
--- a/courses/j1.txt Wed Aug 13 10:31:24 2025 +0900 +++ b/courses/j1.txt Thu Aug 14 11:27:34 2025 +0900 @@ -34,3 +34,5 @@ You can start. + +Only use Japanese and English.
--- a/src/chat.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/chat.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -16,7 +16,8 @@ local current_user = User.current or error() local Chat = require "site:/lib/Chat.luan" local get_chat_by_id = Chat.get_by_id or error() -local languages = require "site:/lib/languages.luan" +local Utils = require "site:/lib/Utils.luan" +local capitalize = Utils.capitalize or error() return function() @@ -79,18 +80,10 @@ </p> <p><%= chat.language_name() %></p> <p> - <label>Region</label><br> - <select name=language_region> -<% for _, region in ipairs(languages[chat.language].regions) do %> - <option value="<%=region.code%>"><%=region.name%></option> -<% end %> - <select> - </p> - <p> <label>Voice</label><br> <select name=voice> <% for _, voice in ipairs(voices) do %> - <option value="<%=voice.code%>"><%=voice.name%></option> + <option value="<%=voice%>"><%=capitalize(voice)%></option> <% end %> <select> </p>
--- a/src/chat.js Wed Aug 13 10:31:24 2025 +0900 +++ b/src/chat.js Thu Aug 14 11:27:34 2025 +0900 @@ -3,11 +3,10 @@ let chat; function setChat(newChat) { - let audioChanged = chat && (chat.language_region != newChat.language_region || chat.voice != newChat.voice); + let audioChanged = chat && chat.voice != newChat.voice; chat = newChat; document.querySelector('[content] [name]').textContent = chat.name; if(audioChanged) { - let lang = `lang=${chat.language_region}&`; let voice = `voice=${chat.voice}&`; let audios = document.querySelectorAll('audio[src]'); for( let audio of audios ) { @@ -22,7 +21,6 @@ function editChat(name) { let dialog = document.querySelector('dialog[edit]'); dialog.querySelector('input[name=name]').value = chat.name; - dialog.querySelector('select[name=language_region]').value = chat.language_region; dialog.querySelector('select[name=voice]').value = chat.voice; dialog.querySelector('input[name=show_text]').checked = chat.show_text; dialog.querySelector('input[name=autoplay]').checked = chat.autoplay; @@ -67,7 +65,7 @@ } function handleChatMarkdown() { - handleMarkdown(chat.language_region,chat.voice); + handleMarkdown(chat.voice,chat.tts_instructions); } function scrollToEnd() { @@ -100,7 +98,7 @@ textarea.parentNode.scrollIntoViewIfNeeded(false); if( !audio ) audio = document.querySelector('div[buttons] audio'); - audio.src = `/tts.mp3?lang=${chat.language_region}&voice=${chat.voice}&text=${encodeURIComponent(textarea.value)}`; + audio.src = `/tts.mp3?voice=${chat.voice}&instructions=${encodeURIComponent(chat.tts_instructions)}&text=${encodeURIComponent(textarea.value)}`; } function askAi() {
--- a/src/chats.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/chats.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -7,11 +7,11 @@ local Shared = require "site:/lib/Shared.luan" local head = Shared.head or error() local header = Shared.header or error() +local languages = Shared.languages or error() local User = require "site:/lib/User.luan" local current_user = User.current_required or error() local Chat = require "site:/lib/Chat.luan" local chat_search = Chat.search or error() -local languages = require "site:/lib/languages.luan" local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "chats.html" @@ -42,11 +42,10 @@ <h1>Your Chats</h1> <form action="lang_courses.html"> <select name=language> -<% for _, lang in pairs(languages) do - local code = lang.code +<% for code, name in pairs(languages) do local selected = code==select_language and "selected" or "" %> - <option value="<%=code%>" <%=selected%> ><%=lang.name%></option> + <option value="<%=code%>" <%=selected%> ><%=name%></option> <% end %> </select> <input type=submit value="new chat">
--- a/src/edit_course.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/edit_course.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -72,6 +72,9 @@ <h4>AI first message (optional)</h4> <textarea name=ai_first_message oninput="fixTextarea(event.target)"><%=html_encode(course.ai_first_message or "")%></textarea> + <h4>Text to speech instructions</h4> + <textarea name=tts_instructions oninput="fixTextarea(event.target)"><%=html_encode(course.tts_instructions or "")%></textarea> + <input type=submit> <hr>
--- a/src/lang_courses.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/lang_courses.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -6,9 +6,9 @@ local Shared = require "site:/lib/Shared.luan" local head = Shared.head or error() local header = Shared.header or error() +local languages = Shared.languages or error() local Course = require "site:/lib/Course.luan" local course_search = Course.search or error() -local languages = require "site:/lib/languages.luan" local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "chats.html" @@ -35,7 +35,7 @@ <body> <% header() %> <div content> - <h1><%=languages[language].name%> Courses</h1> + <h1><%=languages[language]%> Courses</h1> <form action="edit_course.html"> <input type=hidden name=language value="<%=language%>"> <input type=submit value="new course">
--- a/src/lib/Chat.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/lib/Chat.luan Thu Aug 14 11:27:34 2025 +0900 @@ -10,11 +10,11 @@ local Db = require "site:/lib/Db.luan" local run_in_transaction = Db.run_in_transaction or error() local Ai_chat = require "site:/lib/ai/claude/Ai_chat.luan" -local languages = require "site:/lib/languages.luan" 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 languages = Shared.languages or error() local Chat = {} @@ -29,7 +29,7 @@ name = doc.name ai_thread = doc.ai_thread language = doc.language - language_region = doc.language_region + tts_instructions = doc.tts_instructions voice = doc.voice show_text = doc.show_text == "true" autoplay = doc.autoplay == "true" @@ -47,7 +47,7 @@ name = chat.name or error() ai_thread = chat.ai_thread or error() language = chat.language or error() - language_region = chat.language_region or error() + tts_instructions = chat.tts_instructions -- or error() voice = chat.voice or error() show_text = chat.show_text and "true" or "false" autoplay = chat.autoplay and "true" or "false" @@ -55,14 +55,9 @@ } end -local function first_region(language) - return languages[language].regions[1].code -end - 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 + chat.voice = chat.voice or voices[1] if chat.show_text==nil then chat.show_text = true end if chat.autoplay==nil then chat.autoplay = true end @@ -83,8 +78,8 @@ function chat.info() return { id = chat.id - language_region = chat.language_region voice = chat.voice + tts_instructions = chat.tts_instructions name = chat.name show_text = chat.show_text autoplay = chat.autoplay @@ -124,7 +119,7 @@ end function chat.language_name() - return languages[chat.language].name + return languages[chat.language] end return chat
--- a/src/lib/Course.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/lib/Course.luan Thu Aug 14 11:27:34 2025 +0900 @@ -6,7 +6,8 @@ local Html = require "luan:Html.luan" local html_encode = Html.encode or error() local Db = require "site:/lib/Db.luan" -local languages = require "site:/lib/languages.luan" +local Shared = require "site:/lib/Shared.luan" +local languages = Shared.languages or error() local Course = {} @@ -21,6 +22,7 @@ name = doc.name ai_system_prompt = doc.ai_system_prompt ai_first_message = doc.ai_first_message + tts_instructions = doc.tts_instructions } end @@ -34,6 +36,7 @@ name = course.name or error() ai_system_prompt = course.ai_system_prompt or error() ai_first_message = course.ai_first_message + tts_instructions = course.tts_instructions } end @@ -50,7 +53,7 @@ end function course.language_name() - return languages[course.language].name + return languages[course.language] end return course
--- a/src/lib/Shared.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/lib/Shared.luan Thu Aug 14 11:27:34 2025 +0900 @@ -62,15 +62,23 @@ end ) end +Shared.languages = { + en = "English" + jp = "Japanese" + ko = "Korean" +} + Shared.voices = { - { - name = "Brandon" - code = "en-US-BrandonMultilingualNeural" - } - { - name = "Jenny" - code = "en-US-JennyMultilingualNeural" - } + "onyx" + "sage" + "alloy" + "ash" + "ballad" + "coral" + "echo" + "fable" + "nova" + "shimmer" } return Shared
--- a/src/lib/Utils.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/lib/Utils.luan Thu Aug 14 11:27:34 2025 +0900 @@ -2,6 +2,9 @@ local error = Luan.error local pairs = Luan.pairs or error() local type = Luan.type or error() +local String = require "luan:String.luan" +local to_upper = String.upper or error() +local substring = String.sub or error() local Http = require "luan:http/Http.luan" @@ -47,4 +50,8 @@ end ]] +function Utils.capitalize(s) + return to_upper(substring(s,1,1))..substring(s,2) +end + return Utils
--- a/src/lib/languages.luan Wed Aug 13 10:31:24 2025 +0900 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -local Luan = require "luan:Luan.luan" -local error = Luan.error -local pairs = Luan.pairs or error() -local ipairs = Luan.ipairs or error() - - --- https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts - -local regions = { - GB = "United Kingdom" - JP = "Japan" - KR = "Korea" - US = "United States" -} - -local languages = { - en = { - name = "English" - regions = {"US","GB"} - } - jp = { - name = "Japanese" - regions = {"JP"} - } - ko = { - name = "Korean" - regions = {"KR"} - } -} - -for code, info in pairs(languages) do - info.code = code - local t = {nil} - for _, region in ipairs(info.regions) do - t[#t+1] = { - code = code.."-"..region - name = regions[region] or error(region) - } - end - info.regions = t -end - -return languages
--- a/src/new_chat.red.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/new_chat.red.luan Thu Aug 14 11:27:34 2025 +0900 @@ -20,6 +20,7 @@ course_id = course.id name = course.name language = course.language + tts_instructions = course.tts_instructions ai_thread = ai_init(course.ai_system_prompt) } chat.save()
--- a/src/private/Config_sample.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/private/Config_sample.luan Thu Aug 14 11:27:34 2025 +0900 @@ -8,10 +8,6 @@ claude = { key = "sk-xxx" } - azure_tts = { - key = "xxx" - region = "eastus" - } chatgpt = { key = "sk-xxx" }
--- a/src/private/tools/chat.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/private/tools/chat.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -41,7 +41,7 @@ </div> <% if process_markdown then %> <script> - handleMarkdown(<%=json_string(chat.language_region)%>,<%=json_string(chat.voice)%>); + handleMarkdown(<%=json_string(chat.voice)%>,<%=json_string(chat.tts_instructions)%>); </script> <% end %> </body>
--- a/src/save_chat.js.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/save_chat.js.luan Thu Aug 14 11:27:34 2025 +0900 @@ -17,7 +17,6 @@ return function() local chat = Http.request.parameters.chat or error() local name = Http.request.parameters.name or error() - local language_region = Http.request.parameters.language_region or error() local voice = Http.request.parameters.voice or error() local show_text = Http.request.parameters.show_text local autoplay = Http.request.parameters.autoplay @@ -26,7 +25,6 @@ chat = get_chat_by_id(chat) or error() chat.user_id == current_user().id or error() chat.name = name - chat.language_region = language_region chat.voice = voice chat.show_text = show_text ~= nil chat.autoplay = autoplay ~= nil
--- a/src/save_course.js.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/save_course.js.luan Thu Aug 14 11:27:34 2025 +0900 @@ -30,6 +30,7 @@ course.name = parameters.name or error() course.ai_system_prompt = parameters.ai_system_prompt or error() course.ai_first_message = parameters.ai_first_message or error() + course.tts_instructions = parameters.tts_instructions or error() course.updated = time_now() course.save() end )
--- a/src/site.js Wed Aug 13 10:31:24 2025 +0900 +++ b/src/site.js Thu Aug 14 11:27:34 2025 +0900 @@ -151,7 +151,7 @@ } } -function handleMarkdown(lang,voice) { +function handleMarkdown(voice,instructions) { let converter = window.markdownit(); let divs = document.querySelectorAll('[markdown]'); for( let div of divs ) { @@ -166,7 +166,7 @@ rt.remove(); } //console.log(mdDiv.textContent); - parent.querySelector('audio').src = `/tts.mp3?lang=${lang}&voice=${voice}&text=${encodeURIComponent(mdDiv.textContent)}`; + parent.querySelector('audio').src = `/tts.mp3?voice=${voice}&instructions=${encodeURIComponent(instructions)}&text=${encodeURIComponent(mdDiv.textContent)}`; } } }
--- a/src/tts.mp3.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/tts.mp3.luan Thu Aug 14 11:27:34 2025 +0900 @@ -1,43 +1,42 @@ local Luan = require "luan:Luan.luan" local error = Luan.error local Parsers = require "luan:Parsers.luan" -local xml_encode = Parsers.xml_encode or error() +local json_string = Parsers.json_string or error() local Io = require "luan:Io.luan" local uri = Io.uri or error() local Http = require "luan:http/Http.luan" local Config = require "site:/private/Config.luan" +local key = Config.chatgpt.key or error() +local Logging = require "luan:logging/Logging.luan" +local logger = Logging.logger "tts.js" --- https://learn.microsoft.com/en-us/azure/ai-services/speech-service/index-text-to-speech +-- https://platform.openai.com/docs/guides/text-to-speech -local region = Config.azure_tts.region or error() -local url = "https://"..region..".tts.speech.microsoft.com/cognitiveservices/v1" +local url = "https://api.openai.com/v1/audio/speech" local headers = { - ["Ocp-Apim-Subscription-Key"] = Config.azure_tts.key or error() - ["Content-Type"] = "application/ssml+xml" - ["X-Microsoft-OutputFormat"] = "audio-16khz-128kbitrate-mono-mp3" + Authorization = "Bearer "..key + ["Content-Type"] = "application/json" } -local function text_to_speech(lang,voice,text) - local xml = `%> -<speak version='1.0' xml:lang='<%=lang%>'> - <voice name='<%=voice%>'> -<%= xml_encode(text) %> - </voice> -</speak> -<% ` +local function text_to_speech(voice,instructions,text) local options = { method = "POST" headers = headers - content = xml + content = json_string{ + model = "gpt-4o-mini-tts" + voice = voice + input = text + instructions = instructions + } } return uri(url,options) end return function() - local lang = Http.request.parameters.lang or error() local voice = Http.request.parameters.voice or error() + local instructions = Http.request.parameters.instructions or error() local text = Http.request.parameters.text or error() - local input = text_to_speech(lang,voice,text) + local input = text_to_speech(voice,instructions,text) Http.response.binary_writer().write_from(input) end
--- a/src/your_courses.html.luan Wed Aug 13 10:31:24 2025 +0900 +++ b/src/your_courses.html.luan Thu Aug 14 11:27:34 2025 +0900 @@ -7,11 +7,11 @@ local Shared = require "site:/lib/Shared.luan" local head = Shared.head or error() local header = Shared.header or error() +local languages = Shared.languages or error() local User = require "site:/lib/User.luan" local current_user = User.current_required or error() local Course = require "site:/lib/Course.luan" local course_search = Course.search or error() -local languages = require "site:/lib/languages.luan" local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "chats.html" @@ -42,11 +42,10 @@ <h1>Your Courses</h1> <form action="edit_course.html"> <select name=language> -<% for _, lang in pairs(languages) do - local code = lang.code +<% for code, name in pairs(languages) do local selected = code==select_language and "selected" or "" %> - <option value="<%=code%>" <%=selected%> ><%=lang.name%></option> + <option value="<%=code%>" <%=selected%> ><%=name%></option> <% end %> </select> <input type=submit value="new course">