Mercurial Hosting > lang
changeset 14:47b00cce8b53
stt
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Tue, 29 Jul 2025 00:12:05 -0600 |
parents | 65bd7e245c63 |
children | 49e9138b5460 |
files | src/chat.css src/chat.html.luan src/chat.js src/private/Config_sample.luan src/stt.js.luan src/tools/cookies.html.luan |
diffstat | 6 files changed, 102 insertions(+), 12 deletions(-) [+] |
line wrap: on
line diff
diff -r 65bd7e245c63 -r 47b00cce8b53 src/chat.css --- a/src/chat.css Thu Jul 24 22:47:48 2025 -0600 +++ b/src/chat.css Tue Jul 29 00:12:05 2025 -0600 @@ -18,20 +18,20 @@ display: none; } -[ai_container] div[ask] { - padding-top: 1em; - padding-bottom: 1em; - xpadding-left: 12px; - xpadding-right: 12px; - display: flex; - gap: 8px; - align-items: flex-end; -} [ai_container] textarea { - flex-grow: 1; + display: block; + width: 100%; + margin-top: 36px; max-height: 150px; resize: none; } +[ai_container] div[buttons] { + display: flex; + justify-content: flex-end; + margin-top: 8px; + padding-bottom: 8px; + gap: 8px; +} dialog[rename] input { width: 300px;
diff -r 65bd7e245c63 -r 47b00cce8b53 src/chat.html.luan --- a/src/chat.html.luan Thu Jul 24 22:47:48 2025 -0600 +++ b/src/chat.html.luan Tue Jul 29 00:12:05 2025 -0600 @@ -62,7 +62,10 @@ </div> <div ask> <textarea autofocus oninput="fixTextarea(event.target)" onkeydown="textareaKey(event)"></textarea> - <button onclick="askAi()" title="Send"><img src="/images/send.svg"></button> + <div buttons> + <button record onclick="toggleRecording()">Record</button> + <button onclick="askAi()" title="Send"><img src="/images/send.svg"></button> + </div> </div> </div> <img waiting-ai-icon src="/images/spinner_green.gif">
diff -r 65bd7e245c63 -r 47b00cce8b53 src/chat.js --- a/src/chat.js Thu Jul 24 22:47:48 2025 -0600 +++ b/src/chat.js Tue Jul 29 00:12:05 2025 -0600 @@ -71,7 +71,7 @@ function fixTextarea(textarea) { textarea.style.height = 'initial'; textarea.style.height = (textarea.scrollHeight+2) + 'px'; - textarea.scrollIntoViewIfNeeded(false); + textarea.parentNode.scrollIntoViewIfNeeded(false); } function askAi() { @@ -82,3 +82,42 @@ fixTextarea(input); showWaitingAiIcon(); } + + +function setText(text) { + let textarea = document.querySelector('textarea'); + textarea.value = text; + fixTextarea(textarea); +} + +let recorder = null; +let chunks; + +function startRecording() { + chunks = []; + function record(stream) { + recorder = new MediaRecorder( stream, { mimeType: 'audio/webm;codecs=opus' } ); + recorder.ondataavailable = function(event) { + chunks.push(event.data); + }; + recorder.onstop = function(event) { + recorder = null; + let blob = new Blob(chunks, { type: 'audio/webm' }); + let formData = new FormData(); + formData.append('audio', blob, 'recording.webm'); + ajax('stt.js',formData); + document.querySelector('button[record]').textContent = 'Record'; + }; + recorder.start(); + } + navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1 } }).then(record); + document.querySelector('button[record]').textContent = 'Stop Recording'; +} + +function toggleRecording() { + if( recorder === null ) { + startRecording(); + } else { + recorder.stop(); + } +}
diff -r 65bd7e245c63 -r 47b00cce8b53 src/private/Config_sample.luan --- a/src/private/Config_sample.luan Thu Jul 24 22:47:48 2025 -0600 +++ b/src/private/Config_sample.luan Tue Jul 29 00:12:05 2025 -0600 @@ -12,4 +12,7 @@ key = "xxx" region = "eastus" } + chatgpt = { + key = "sk-xxx" + } }
diff -r 65bd7e245c63 -r 47b00cce8b53 src/stt.js.luan --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/stt.js.luan Tue Jul 29 00:12:05 2025 -0600 @@ -0,0 +1,44 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local Parsers = require "luan:Parsers.luan" +local json_string = Parsers.json_string or error() +local json_parse = Parsers.json_parse 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 "sst.js" + + +local url = "https://api.openai.com/v1/audio/transcriptions" +local headers = { + Authorization = "Bearer "..key + ["Content-Type"] = "multipart/form-data" +} + +local function speech_to_text(audio) + local options = { + method = "POST" + headers = headers + enctype = "multipart/form-data" + parameters = { + model = "whisper-1" + file = audio + } + } + local json = uri(url,options).read_text() + -- logger.info(json) + local result = json_parse(json) + return result.text or error() +end + +return function() + local audio = Http.request.parameters.audio or error() + local text = speech_to_text(audio) + Io.stdout = Http.response.text_writer() +%> + setText(<%=json_string(text)%>); +<% +end