diff --git a/app.py b/app.py
index acb7f3e..f97d6d5 100644
--- a/app.py
+++ b/app.py
@@ -7,6 +7,7 @@ from flask import Flask, jsonify, request, render_template_string
OLLAMA_URL = os.environ.get("LYRA_OLLAMA_URL", "http://10.30.20.222:11434")
TEXT_MODEL = os.environ.get("LYRA_TEXT_MODEL", "ornith-1.5:9b-64k")
VISION_MODEL = os.environ.get("LYRA_VISION_MODEL", "minicpm-v4.5:8b")
+KOKORO_URL = os.environ.get("LYRA_KOKORO_URL", "http://10.30.20.85:8766")
app = Flask(__name__)
@@ -327,8 +328,10 @@ input[type=range]{width:150px}
Settings ✦ saved on this device
Voice engine
-
-
Voice
+
+ Kokoro voice
+
+ Browser voice
Speech rate 1.0
Pitch 1.0
Lyra text model
@@ -374,8 +377,8 @@ speechSynthesis.onvoiceschanged=loadVoices;loadVoices();
function speak(text,cb){
if(!$('voiceDot'))return;const dot=$('voiceDot');dot.classList.add('live');
const done=()=>{dot.classList.remove('live');if(cb)cb()};
- if($('setEngine').value==='piper'){
- fetch('/api/tts',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text})})
+ if($('setEngine').value==='kokoro'){
+ fetch('/api/tts',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text,voice:$('setKVoice').value})})
.then(r=>r.ok?r.blob():Promise.reject()).then(b=>{const a=new Audio(URL.createObjectURL(b));a.onended=done;a.play()})
.catch(()=>{browserSpeak(text,done)});
}else browserSpeak(text,done);
@@ -508,7 +511,7 @@ function timerReset(){clearInterval(tInt);clearInterval(bInt);tInt=null;$('timer
/* ---------- settings ---------- */
function loadSettings(){
- $('setEngine').value=S.engine||'browser';$('setVoice').value=S.voice||$('setVoice').value;
+ $('setEngine').value=S.engine||'kokoro';$('setKVoice').value=S.kvoice||'af_heart';$('setVoice').value=S.voice||$('setVoice').value;
$('setRate').value=S.rate||1;$('setPitch').value=S.pitch||1;
$('setTModel').value=S.tmodel||'qwen3.5:4b';$('setVModel').value=S.vmodel||'minicpm-v4.5:8b';
$('setHost').value=S.host||'http://10.30.20.222:11434';$('setAutoEvery').value=S.autoEvery||45;
@@ -516,10 +519,10 @@ function loadSettings(){
$('rateV').textContent=$('setRate').value;$('pitchV').textContent=$('setPitch').value;$('aeV').textContent=$('setAutoEvery').value;
applyWarm();}
function saveSettings(){
- S.engine=$('setEngine').value;S.voice=$('setVoice').value;S.rate=$('setRate').value;S.pitch=$('setPitch').value;
+ S.engine=$('setEngine').value;S.kvoice=$('setKVoice').value;S.voice=$('setVoice').value;S.rate=$('setRate').value;S.pitch=$('setPitch').value;
S.tmodel=$('setTModel').value;S.vmodel=$('setVModel').value;S.host=$('setHost').value;S.autoEvery=+$('setAutoEvery').value;
S.warm=$('setWarm').value;S.autoMode=$('autoMode').checked;S.autoSpeak=$('autoSpeak').checked;
- saveS();applyWarm();$('engineTag').textContent=$('setEngine').value==='piper'?'piper → browser fallback':'browser voice';
+ saveS();applyWarm();applyEngineTag();
toast('Saved');autoDirector();}
function testVoice(){speak('Ready when you are. She looks incredible.')}
$('setRate').oninput=()=>$('rateV').textContent=$('setRate').value;
@@ -528,7 +531,8 @@ $('setAutoEvery').oninput=()=>$('aeV').textContent=$('setAutoEvery').value;
function applyWarm(){const w=+$('setWarm').value;document.body.style.filter=`sepia(${w/400})`}
$('setWarm').oninput=applyWarm;
loadSettings();
-$('engineTag').textContent=S.engine==='piper'?'piper → browser fallback':'browser voice';
+function applyEngineTag(){$('engineTag').textContent=$('setEngine').value==='kokoro'?'kokoro · '+$('setKVoice').value:'browser voice'}
+applyEngineTag();
setTimeout(autoDirector,800);