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
--- 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;
--- 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">
--- 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();
+	}
+}
--- 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"
+	}
 }
--- /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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tools/cookies.html.luan	Tue Jul 29 00:12:05 2025 -0600
@@ -0,0 +1,1 @@
+return require "luan:http/tools/cookies.html.luan"