view src/chat.js @ 67:1793510fa36a

course description
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 22 Aug 2025 10:28:13 -0600
parents 27758f3b2d69
children f5e72f2d1025
line wrap: on
line source

'use strict';

let chat;

function setChat(newChat) {
	let audioChanged = chat && chat.voice != newChat.voice;
	chat = newChat;
	document.querySelector('[content] [name]').textContent = chat.name;
	if(audioChanged) {
		let voice = `voice=${chat.voice}&`;
		let audios = document.querySelectorAll('audio[src]');
		for( let audio of audios ) {
			let src = audio.src;
			src = src.replace(/lang=[^&]+&/,lang);
			src = src.replace(/voice=[^&]+&/,voice);
			audio.src = src;
		}
	}
}

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;
	if( chat.show_text )
		dialog.querySelector('select[name=show_text]').value = chat.show_text;
	dialog.querySelector('input[name=autoplay]').checked = chat.autoplay;
	dialog.querySelector('input[name=is_private]').checked = chat.is_private;
	dialog.showModal();
}

function saveChat(form) {
	closeModal(form);
	ajaxForm('save_chat.js',form);
}

function deleteChat() {
	let dialog = document.querySelector('dialog[delete]');
	dialog.showModal();
}

function doDeleteChat(el) {
	closeModal(el);
	ajax(`delete_chat.js?chat=${chat.id}`);
}

function systemPrompt() {
	let dialog = document.querySelector('dialog[system_prompt]');
	dialog.showModal();
}

function showWaitingAiIcon() {
	document.querySelector('[waiting-ai-icon]').style.display = 'block';
}

function hideWaitingAiIcon() {
	document.querySelector('[waiting-ai-icon]').style.display = 'none';
}

function playLastMessage() {
	let audios = document.querySelectorAll('[messages] audio');
	if( audios.length >= 1 ) {
		let audio = audios[audios.length-1];
		audio.play();
	}
}

function handleChatMarkdown() {
	handleMarkdown(chat.voice,chat.tts_instructions);
}

function scrollToEnd() {
	window.scrollTo(0, document.body.scrollHeight);
}

function updateAi(html) {
	hideWaitingAiIcon();
	document.querySelector('div[messages]').insertAdjacentHTML('beforeend',html);
	handleChatMarkdown();
	document.querySelector('textarea').focus();
	scrollToEnd();
	if( chat.autoplay )
		playLastMessage();
}

const isMobile = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;

function textareaKey(event) {
	if( event.keyCode===13 && !event.shiftKey && !event.ctrlKey && !isMobile ) {
		event.preventDefault();
		askAi();
	}
}

let audio;

function fixChatTextarea(textarea) {
	fixTextarea(textarea);
	textarea.parentNode.scrollIntoViewIfNeeded(false);
	if( !audio )
		audio = document.querySelector('div[buttons] audio');
	audio.src = `/tts.mp3?voice=${chat.voice}&instructions=${encodeURIComponent(chat.tts_instructions)}&text=${encodeURIComponent(textarea.value)}`;
}

function askAi() {
	let input = document.querySelector('textarea');
	let url = `ai_ask.js?chat=${chat.id}&input=${encodeURIComponent(input.value)}`;
	ajax(url);
	input.value = '';
	fixChatTextarea(input);
	showWaitingAiIcon();
}


function setText(text) {
	let textarea = document.querySelector('textarea');
	textarea.value = text;
	fixChatTextarea(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();
	}
}