|
|
|
SpracheVon $1Inhaltsverzeichniskeine Gliederung○ Text→Sprache ○ Sprache→Sprache ○ Sprache→Text ☎ Helikon anrufen ● Wiki-Suche ● Aufnehmen 0s (function() {
/* === INJECT CSS === */
var css = ''
+ '#helikonTTS { max-width:700px; margin:0; font-family:"Courier New",Courier,monospace; }'
+ '#ttsControls { display:flex; align-items:center; gap:8px; padding:4px 0; flex-wrap:wrap; }'
+ '.voice-btn { background:#fff; border:2px solid #0077aa; color:#0077aa; padding:8px 14px; cursor:pointer; font-family:inherit; font-size:12px; display:inline-block; border-radius:4px; }'
+ '.voice-btn:hover { background:#0077aa; color:#fff; }'
+ '.voice-btn.active { background:#0077aa; color:#fff; }'
+ '.voice-btn.disabled { background:#eee; border-color:#ccc; color:#aaa; cursor:default; }'
+ '.voice-btn.disabled:hover { background:#eee; color:#aaa; }'
+ '#callToggle { border-color:#3a8f3a; color:#3a8f3a; }'
+ '#callToggle:hover { background:#3a8f3a; color:#fff; }'
+ '#callToggle.active { background:#3a8f3a; color:#fff; box-shadow:0 0 12px rgba(58,143,58,0.5); animation:callGlow 2s infinite; }'
+ '@keyframes callGlow { 0%,100% { box-shadow:0 0 8px rgba(58,143,58,0.4); } 50% { box-shadow:0 0 20px rgba(58,143,58,0.8); } }'
+ '#searchToggle { border-color:#cc8800; color:#cc8800; }'
+ '#searchToggle:hover { background:#cc8800; color:#fff; }'
+ '#searchToggle.active { background:#cc8800; color:#fff; }'
+ '#ttsStatus { color:#999; font-size:11px; margin-left:4px; }'
+ '#voiceRecBar { display:none; align-items:center; gap:10px; padding:6px 0; margin-top:4px; }'
+ '#voiceRecBar.visible { display:flex; }'
+ '#voiceRecBtn { background:#fff; border:2px solid #cc3333; color:#cc3333; padding:8px 14px; cursor:pointer; font-family:inherit; font-size:12px; display:inline-block; border-radius:4px; }'
+ '#voiceRecBtn:hover { background:#cc3333; color:#fff; }'
+ '#voiceRecBtn.recording { background:#cc3333; color:#fff; animation:recPulse 1s infinite; }'
+ '@keyframes recPulse { 0%,100% { opacity:1; } 50% { opacity:0.6; } }'
+ '#voiceRecTime { color:#cc3333; font-size:12px; min-width:30px; }'
+ '#voiceRecStatus { color:#999; font-size:11px; }'
+ '#callBar { display:none; align-items:center; gap:10px; padding:6px 0; margin-top:4px; }'
+ '#callBar.visible { display:flex; }'
+ '#callIndicator { width:10px; height:10px; border-radius:50%; background:#999; display:inline-block; }'
+ '#callIndicator.listening { background:#4CAF50; animation:listenPulse 1.5s infinite; }'
+ '#callIndicator.processing { background:#ff9800; animation:procPulse 0.5s infinite; }'
+ '#callIndicator.speaking { background:#0077aa; animation:speakPulse 0.8s infinite; }'
+ '#callIndicator.waiting { background:#999; }'
+ '@keyframes listenPulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:0.6; transform:scale(1.3); } }'
+ '@keyframes procPulse { 0%,100% { opacity:1; } 50% { opacity:0.3; } }'
+ '@keyframes speakPulse { 0%,100% { opacity:1; } 50% { opacity:0.7; } }'
+ '#callStatus { color:#666; font-size:12px; }'
+ '.tts-play-btn { background:#fff; border:1px solid #ccc; color:#999; padding:2px 8px; cursor:pointer; font-size:11px; margin-left:8px; font-family:"Courier New",Courier,monospace; display:none; }'
+ '.tts-play-btn:hover { border-color:#0077aa; color:#0077aa; }'
+ '.tts-play-btn.playing { border-color:#cc6600; color:#cc6600; display:inline-block !important; }'
+ '.tts-active .tts-play-btn { display:inline-block; }';
var styleEl = document.createElement('style');
styleEl.type = 'text/css';
if (styleEl.styleSheet) {
styleEl.styleSheet.cssText = css;
} else {
styleEl.appendChild(document.createTextNode(css));
}
document.getElementsByTagName('head')[0].appendChild(styleEl);
/* === DOM === */
var ttsBtn = document.getElementById('ttsToggle');
var stsBtn = document.getElementById('stsToggle');
var sttBtn = document.getElementById('sttToggle');
var callBtn = document.getElementById('callToggle');
var searchBtn = document.getElementById('searchToggle');
var ttsStatus = document.getElementById('ttsStatus');
var recBar = document.getElementById('voiceRecBar');
var recBtn = document.getElementById('voiceRecBtn');
var recTime = document.getElementById('voiceRecTime');
var recStatus = document.getElementById('voiceRecStatus');
var callBarEl = document.getElementById('callBar');
var callIndic = document.getElementById('callIndicator');
var callStatus = document.getElementById('callStatus');
if (!ttsBtn) return;
/* Apply button classes */
ttsBtn.className = 'voice-btn';
stsBtn.className = 'voice-btn disabled';
sttBtn.className = 'voice-btn';
callBtn.className = 'voice-btn';
searchBtn.className = 'voice-btn active';
/* === STATE === */
var ttsActive = false;
var stsActive = false;
var sttActive = false;
var callActive = false;
var searchActive = true; /* DEFAULT ON */
var ttsApiKey = '';
var voiceId = '';
var modelId = 'eleven_multilingual_v2';
var currentAudio = null;
var isSpeaking = false;
var isRecording = false;
var mediaRecorder = null;
var audioChunks = [];
var recTimer = null;
var recSeconds = 0;
/* Call mode state */
var callStream = null;
var callAnalyser = null;
var callAudioCtx = null;
var callSilenceTimer = null;
var callPhase = 'idle'; /* idle, listening, processing, speaking, waiting */
var SILENCE_THRESHOLD = 15; /* Volume level below which = silence */
var SILENCE_DURATION = 1800; /* ms of silence before auto-stop */
/* === GET PAGE ID === */
var pageId = 0;
if (typeof Deki !== 'undefined' && Deki.PageId) {
pageId = Deki.PageId;
}
/* === FETCH ALL PROPERTIES === */
function fetchAllProperties(callback) {
if (!pageId) { callback(new Object()); return; }
var url = '/@api/deki/pages/' + pageId + '/properties';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onload = function() {
var props = new Object();
if (xhr.status === 200) {
try {
var parser = new DOMParser();
var xml = parser.parseFromString(xhr.responseText, 'text/xml');
var entries = xml.getElementsByTagName('property');
for (var i = 0; i < entries.length; i++) {
var nameEl = entries[i].getAttribute('name');
if (!nameEl) continue;
var short = nameEl.replace('urn:custom.mindtouch.com#', '');
var contentsEl = entries[i].getElementsByTagName('contents');
if (contentsEl.length > 0) {
var href = contentsEl[0].getAttribute('href');
if (href) { props['_href_' + short] = href; }
var textNode = contentsEl[0].textContent;
if (textNode) { props[short] = textNode.replace(/^\s+|\s+$/g, ''); }
}
}
} catch(e) {}
}
var hrefKeys = [];
for (var k in props) {
if (k.indexOf('_href_') === 0) { hrefKeys.push(k.replace('_href_', '')); }
}
if (hrefKeys.length === 0) { callback(props); return; }
var done = 0;
for (var h = 0; h < hrefKeys.length; h++) {
(function(propName) {
var xh = new XMLHttpRequest();
xh.open('GET', props['_href_' + propName], true);
xh.onload = function() {
if (xh.status === 200) { props[propName] = xh.responseText.replace(/^\s+|\s+$/g, ''); }
done++;
if (done === hrefKeys.length) callback(props);
};
xh.onerror = function() { done++; if (done === hrefKeys.length) callback(props); };
xh.send();
})(hrefKeys[h]);
}
};
xhr.onerror = function() { callback(new Object()); };
xhr.send();
}
/* === UI HELPERS === */
function setStatus(msg) { if (ttsStatus) ttsStatus.textContent = msg; }
function setRecStatus(msg) { if (recStatus) recStatus.textContent = msg; }
function setCallStatus(msg) { if (callStatus) callStatus.textContent = msg; }
function setCallPhase(phase) {
callPhase = phase;
if (callIndic) callIndic.className = phase;
var labels = new Object();
labels.idle = '';
labels.listening = 'hoert zu...';
labels.processing = 'verarbeite...';
labels.speaking = 'Helikon spricht...';
labels.waiting = 'wartet auf Antwort...';
setCallStatus(labels[phase] || '');
}
function updateButtons() {
ttsBtn.className = 'voice-btn' + (ttsActive ? ' active' : '');
ttsBtn.innerHTML = (ttsActive ? '●' : '○') + ' Text→Sprache';
stsBtn.className = 'voice-btn disabled';
stsBtn.innerHTML = '○ Sprache→Sprache';
sttBtn.className = 'voice-btn' + (sttActive ? ' active' : '');
sttBtn.innerHTML = (sttActive ? '●' : '○') + ' Sprache→Text';
callBtn.className = 'voice-btn' + (callActive ? ' active' : '');
callBtn.innerHTML = callActive ? '☎ Auflegen' : '☎ Helikon anrufen';
searchBtn.className = 'voice-btn' + (searchActive ? ' active' : '');
searchBtn.innerHTML = (searchActive ? '●' : '○') + ' Wiki-Suche';
/* Global flag for Search Add-on */
window.helikonSearchEnabled = searchActive;
/* Show/hide bars */
if (sttActive) {
recBar.className = 'visible';
callBarEl.className = '';
} else if (callActive) {
recBar.className = '';
callBarEl.className = 'visible';
} else {
recBar.className = '';
callBarEl.className = '';
}
/* TTS play buttons */
var chatLog = document.getElementById('chatLog');
if (chatLog) {
if (ttsActive || callActive) {
chatLog.className = chatLog.className.replace(' tts-active', '') + ' tts-active';
} else {
chatLog.className = chatLog.className.replace(' tts-active', '');
stopAudio();
}
}
}
/* === ADD CHAT MESSAGE === */
function addChatMsg(text, type) {
var chatLog = document.getElementById('chatLog');
if (!chatLog) return;
var div = document.createElement('div');
div.className = 'chatMsg ' + type;
div.textContent = text;
chatLog.appendChild(div);
chatLog.scrollTop = chatLog.scrollHeight;
}
/* === CLEAN TEXT === */
function cleanText(text) {
var LT = String.fromCharCode(60);
var GT = String.fromCharCode(62);
var LBRACE = String.fromCharCode(123);
var RBRACE = String.fromCharCode(125);
var htmlTagRe = new RegExp(LT + '[^' + GT + ']*' + GT, 'g');
var headingRe = new RegExp('#' + LBRACE + '1,6' + RBRACE + '\\s', 'g');
var linkRe = new RegExp('\\[([^\\]]+)\\]\\([^)]+\\)', 'g');
var clean = text
.replace(htmlTagRe, '')
.replace(/\*\*([^*]+)\*\*/g, '$1')
.replace(/\*([^*]+)\*/g, '$1')
.replace(/`([^`]+)`/g, '$1')
.replace(headingRe, '')
.replace(linkRe, '$1')
.replace(/\s+/g, ' ')
.replace(/^\s+|\s+$/g, '');
if (clean.indexOf('>') === 0) { clean = clean.substring(1).replace(/^\s+/, ''); }
return clean;
}
/* ============================================================
TEXT TO SPEECH (TTS)
============================================================ */
function speak(text, onDone) {
if (!ttsApiKey || !voiceId) {
setStatus('TTS nicht konfiguriert');
if (onDone) onDone();
return;
}
var clean = cleanText(text);
if (!clean) { if (onDone) onDone(); return; }
if (clean.length > 5000) { clean = clean.substring(0, 5000); }
if (typeof console !== 'undefined') {
console.log('TTS speak: "' + clean.substring(0, 60) + '..." (' + clean.length + ' chars)');
}
setStatus('spreche...');
isSpeaking = true;
var url = 'https://api.elevenlabs.io/v1/text-to-speech/' + encodeURIComponent(voiceId)
+ '?output_format=mp3_44100_128';
var bodyObj = new Object();
bodyObj.text = clean;
bodyObj.model_id = modelId;
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'blob';
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('xi-api-key', ttsApiKey);
xhr.onload = function() {
if (xhr.status === 200) {
playBlob(xhr.response, onDone);
} else {
isSpeaking = false;
setStatus('TTS-Fehler ' + xhr.status);
if (onDone) onDone();
}
};
xhr.onerror = function() {
isSpeaking = false;
setStatus('TTS nicht erreichbar');
if (onDone) onDone();
};
xhr.send(JSON.stringify(bodyObj));
}
/* === AUDIO PLAYBACK HELPER === */
function playBlob(blob, onDone) {
var audioUrl = URL.createObjectURL(blob);
stopAudio();
currentAudio = new Audio(audioUrl);
currentAudio.onended = function() {
isSpeaking = false;
setStatus('bereit');
URL.revokeObjectURL(audioUrl);
currentAudio = null;
if (onDone) onDone();
};
currentAudio.onerror = function() {
isSpeaking = false;
setStatus('Wiedergabefehler');
URL.revokeObjectURL(audioUrl);
currentAudio = null;
if (onDone) onDone();
};
currentAudio.play().catch(function() {
setStatus('Autoplay blockiert');
isSpeaking = false;
if (onDone) onDone();
});
}
function stopAudio() {
if (currentAudio) {
currentAudio.pause();
currentAudio.currentTime = 0;
currentAudio = null;
}
isSpeaking = false;
}
/* ============================================================
MICROPHONE RECORDING (for STT manual mode)
============================================================ */
function startRecording(onComplete) {
if (isRecording) return;
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
setRecStatus('Mikrofon nicht verfuegbar');
return;
}
audioChunks = [];
recSeconds = 0;
recTime.textContent = '0s';
var constraints = new Object();
constraints.audio = true;
constraints.video = false;
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
mediaRecorder = new MediaRecorder(stream);
isRecording = true;
recBtn.className = 'recording';
recBtn.innerHTML = '■ Stopp';
setRecStatus('aufnahme...');
mediaRecorder.ondataavailable = function(e) {
if (e.data.size > 0) { audioChunks.push(e.data); }
};
mediaRecorder.onstop = function() {
isRecording = false;
recBtn.className = '';
recBtn.innerHTML = '● Aufnehmen';
clearInterval(recTimer);
recTimer = null;
var tracks = stream.getTracks();
for (var t = 0; t < tracks.length; t++) { tracks[t].stop(); }
if (audioChunks.length > 0) {
var blobType = new Object();
blobType.type = 'audio/webm';
var blob = new Blob(audioChunks, blobType);
if (onComplete) onComplete(blob);
}
};
mediaRecorder.start();
recTimer = setInterval(function() {
recSeconds++;
recTime.textContent = recSeconds + 's';
if (recSeconds >= 30) { stopRecording(); }
}, 1000);
}).catch(function() {
setRecStatus('Mikrofon verweigert');
});
}
function stopRecording() {
if (mediaRecorder && isRecording) { mediaRecorder.stop(); }
if (recTimer) { clearInterval(recTimer); recTimer = null; }
}
/* ============================================================
SPEECH TO TEXT (STT)
============================================================ */
function transcribeAudio(blob, callback) {
var formData = new FormData();
formData.append('file', blob, 'recording.webm');
formData.append('model_id', 'scribe_v2');
formData.append('language_code', 'de');
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.elevenlabs.io/v1/speech-to-text', true);
xhr.setRequestHeader('xi-api-key', ttsApiKey);
xhr.onload = function() {
if (xhr.status === 200) {
try {
var result = JSON.parse(xhr.responseText);
var text = result.text || '';
if (typeof console !== 'undefined') { console.log('STT: "' + text + '"'); }
if (callback) callback(text);
} catch(e) {
if (callback) callback('');
}
} else {
if (typeof console !== 'undefined') { console.log('STT error:', xhr.status); }
setRecStatus('STT-Fehler ' + xhr.status);
if (callback) callback('');
}
};
xhr.onerror = function() {
setRecStatus('STT nicht erreichbar');
if (callback) callback('');
};
xhr.send(formData);
}
/* ============================================================
SPEECH TO SPEECH (STS) — kept but disabled
============================================================ */
function transformVoice(blob) {
setRecStatus('transformiere Stimme...');
var formData = new FormData();
formData.append('audio', blob, 'recording.webm');
formData.append('model_id', 'eleven_english_sts_v2');
var url = 'https://api.elevenlabs.io/v1/speech-to-speech/' + encodeURIComponent(voiceId)
+ '?output_format=mp3_44100_128';
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'blob';
xhr.setRequestHeader('xi-api-key', ttsApiKey);
xhr.onload = function() {
if (xhr.status === 200) {
addChatMsg('[STS] Stimme transformiert', 'system');
playBlob(xhr.response, function() { setRecStatus('bereit'); });
} else {
setRecStatus('STS-Fehler ' + xhr.status);
}
};
xhr.onerror = function() { setRecStatus('STS nicht erreichbar'); };
xhr.send(formData);
}
/* ============================================================
CALL MODE — Voice Conversation Loop
============================================================ */
function callStartListening() {
if (!callActive) return;
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
setCallStatus('Mikrofon nicht verfuegbar');
return;
}
setCallPhase('listening');
audioChunks = [];
var constraints = new Object();
constraints.audio = true;
constraints.video = false;
navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
callStream = stream;
/* Setup audio analyser for silence detection */
callAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source = callAudioCtx.createMediaStreamSource(stream);
callAnalyser = callAudioCtx.createAnalyser();
callAnalyser.fftSize = 512;
source.connect(callAnalyser);
/* Start recording */
mediaRecorder = new MediaRecorder(stream);
isRecording = true;
mediaRecorder.ondataavailable = function(e) {
if (e.data.size > 0) { audioChunks.push(e.data); }
};
mediaRecorder.onstop = function() {
isRecording = false;
};
mediaRecorder.start(250); /* Collect in 250ms chunks */
/* Start silence detection loop */
var silenceStart = null;
var hasSpoken = false;
function checkVolume() {
if (!callActive || callPhase !== 'listening') return;
var dataArray = new Uint8Array(callAnalyser.frequencyBinCount);
callAnalyser.getByteFrequencyData(dataArray);
/* Calculate average volume */
var sum = 0;
for (var vi = 0; vi < dataArray.length; vi++) { sum += dataArray[vi]; }
var avg = sum / dataArray.length;
if (avg > SILENCE_THRESHOLD) {
/* Sound detected */
hasSpoken = true;
silenceStart = null;
} else if (hasSpoken) {
/* Silence after speech */
if (!silenceStart) {
silenceStart = Date.now();
} else if (Date.now() - silenceStart > SILENCE_DURATION) {
/* Silence long enough — process */
callStopListening();
return;
}
}
/* Continue checking */
if (callActive && callPhase === 'listening') {
setTimeout(checkVolume, 100);
}
}
/* Small delay before starting detection to avoid immediate trigger */
setTimeout(checkVolume, 500);
}).catch(function() {
setCallStatus('Mikrofon verweigert');
callActive = false;
updateButtons();
});
}
function callStopListening() {
if (mediaRecorder && isRecording) {
mediaRecorder.stop();
}
/* Stop stream */
if (callStream) {
var tracks = callStream.getTracks();
for (var t = 0; t < tracks.length; t++) { tracks[t].stop(); }
callStream = null;
}
if (callAudioCtx) {
callAudioCtx.close();
callAudioCtx = null;
}
/* Small delay to collect final audio chunks */
setTimeout(function() {
if (audioChunks.length === 0) {
/* No audio — restart listening */
if (callActive) { callStartListening(); }
return;
}
setCallPhase('processing');
var blobType = new Object();
blobType.type = 'audio/webm';
var blob = new Blob(audioChunks, blobType);
/* Transcribe */
transcribeAudio(blob, function(text) {
if (!text || !callActive) {
/* No text recognized — restart */
if (callActive) { callStartListening(); }
return;
}
/* Insert into chat input and send */
var chatInput = document.getElementById('chatInput');
var chatSend = document.getElementById('chatSend');
if (chatInput && chatSend) {
chatInput.value = text;
setCallPhase('waiting');
setCallStatus('wartet auf Antwort...');
/* Click send */
chatSend.click();
/* Now wait for buddy response — handled by chat observer */
}
});
}, 300);
}
function callEndAll() {
callActive = false;
callPhase = 'idle';
if (mediaRecorder && isRecording) { mediaRecorder.stop(); }
if (callStream) {
var tracks = callStream.getTracks();
for (var t = 0; t < tracks.length; t++) { tracks[t].stop(); }
callStream = null;
}
if (callAudioCtx) {
callAudioCtx.close();
callAudioCtx = null;
}
stopAudio();
updateButtons();
setCallStatus('');
setStatus('Anruf beendet');
}
/* ============================================================
PLAY BUTTONS ON BUDDY MESSAGES
============================================================ */
function getBuddyText(el) {
var clone = el.cloneNode(true);
var btns = clone.querySelectorAll('.tts-play-btn');
for (var r = 0; r < btns.length; r++) { btns[r].parentNode.removeChild(btns[r]); }
return clone.textContent || clone.innerText || '';
}
function addPlayButton(msgEl) {
var playBtn = document.createElement('span');
playBtn.className = 'tts-play-btn';
playBtn.innerHTML = '▶ vorlesen';
playBtn.title = 'Nachricht vorlesen';
playBtn.onclick = function() {
if (isSpeaking && playBtn.className.indexOf('playing') !== -1) {
stopAudio();
playBtn.innerHTML = '▶ vorlesen';
playBtn.className = 'tts-play-btn';
setStatus('gestoppt');
return;
}
stopAudio();
var allBtns = document.querySelectorAll('.tts-play-btn.playing');
for (var b = 0; b < allBtns.length; b++) {
allBtns[b].innerHTML = '▶ vorlesen';
allBtns[b].className = 'tts-play-btn';
}
playBtn.innerHTML = '■ stopp';
playBtn.className = 'tts-play-btn playing';
speak(getBuddyText(msgEl), function() {
playBtn.innerHTML = '▶ vorlesen';
playBtn.className = 'tts-play-btn';
});
};
msgEl.appendChild(playBtn);
}
function isBuddyMessage(node) {
if (!node.className) return false;
var cls = node.className;
if (cls.indexOf('chatMsg') !== -1 && cls.indexOf('buddy') !== -1) {
if (cls.indexOf('typing') !== -1) return false;
return true;
}
return false;
}
/* ============================================================
CHAT OBSERVER — handles both TTS autoplay and Call mode
============================================================ */
function watchChat() {
var chatLog = document.getElementById('chatLog');
if (!chatLog) { setTimeout(watchChat, 500); return; }
var existing = chatLog.querySelectorAll('.chatMsg.buddy');
for (var e = 0; e < existing.length; e++) {
if (!existing[e].querySelector('.tts-play-btn')) { addPlayButton(existing[e]); }
}
if (typeof MutationObserver !== 'undefined') {
var observer = new MutationObserver(function(mutations) {
for (var m = 0; m < mutations.length; m++) {
var added = mutations[m].addedNodes;
for (var n = 0; n < added.length; n++) {
var node = added[n];
if (node.nodeType !== 1) continue;
if (isBuddyMessage(node)) {
(function(el) {
setTimeout(function() {
if (!el.querySelector('.tts-play-btn')) { addPlayButton(el); }
var msgText = getBuddyText(el).replace(/^\s+|\s+$/g, '');
/* CALL MODE: speak response, then resume listening */
if (callActive && (callPhase === 'waiting' || callPhase === 'processing')) {
if (msgText) {
setCallPhase('speaking');
speak(msgText, function() {
if (callActive) {
/* Resume listening after TTS done */
setTimeout(function() {
if (callActive) { callStartListening(); }
}, 400);
}
});
} else {
/* Empty response — resume listening */
if (callActive) { callStartListening(); }
}
return;
}
/* Normal TTS autoplay */
if (ttsActive && ttsApiKey && voiceId) {
if (msgText) { speak(msgText); }
}
}, 200);
})(node);
}
}
}
});
var obsConfig = new Object();
obsConfig.childList = true;
obsConfig.subtree = false;
observer.observe(chatLog, obsConfig);
}
}
/* ============================================================
TOGGLE HANDLERS
============================================================ */
ttsBtn.onclick = function() {
if (!ttsApiKey || !voiceId) { setStatus('API-Key/Voice fehlt!'); return; }
if (callActive) return; /* Don't toggle during call */
ttsActive = !ttsActive;
updateButtons();
setStatus(ttsActive ? 'TTS aktiv' : 'TTS aus');
};
stsBtn.onclick = function() {
/* Disabled — do nothing */
setStatus('STS deaktiviert');
};
sttBtn.onclick = function() {
if (!ttsApiKey) { setStatus('API-Key fehlt!'); return; }
if (callActive) return;
sttActive = !sttActive;
updateButtons();
setRecStatus(sttActive ? 'bereit — klick Aufnehmen' : '');
setStatus(sttActive ? 'STT aktiv' : 'STT aus');
};
searchBtn.onclick = function() {
searchActive = !searchActive;
window.helikonSearchEnabled = searchActive;
updateButtons();
setStatus(searchActive ? 'Wiki-Suche aktiv' : 'Wiki-Suche aus');
};
callBtn.onclick = function() {
if (!ttsApiKey || !voiceId) { setStatus('API-Key/Voice fehlt!'); return; }
if (callActive) {
/* Hang up */
callEndAll();
addChatMsg('[Anruf beendet]', 'system');
return;
}
/* Start call */
callActive = true;
ttsActive = false;
sttActive = false;
stsActive = false;
updateButtons();
addChatMsg('[Anruf gestartet — sprich jetzt...]', 'system');
setStatus('Anruf aktiv');
/* Begin listening */
callStartListening();
};
/* === STT RECORD BUTTON === */
recBtn.onclick = function() {
if (isRecording) {
stopRecording();
return;
}
if (sttActive) {
startRecording(function(blob) {
setRecStatus('transkribiere...');
transcribeAudio(blob, function(text) {
if (text) {
var chatInput = document.getElementById('chatInput');
if (chatInput) {
chatInput.value = text;
chatInput.focus();
}
addChatMsg('[STT] ' + text, 'system');
setRecStatus(text.substring(0, 40) + (text.length > 40 ? '...' : ''));
} else {
setRecStatus('kein Text erkannt');
}
});
});
}
};
/* ============================================================
INIT
============================================================ */
setStatus('lade...');
fetchAllProperties(function(props) {
ttsApiKey = props['buddy.ttsapikey'] || '';
voiceId = props['buddy.ttsvoiceid'] || '';
modelId = props['buddy.ttsmodel'] || 'eleven_multilingual_v2';
if (typeof console !== 'undefined') {
console.log('=== HELIKON VOICE v5 ===');
console.log('Key: ' + (ttsApiKey ? ttsApiKey.substring(0,8) + '...' : 'FEHLT'));
console.log('Voice: ' + (voiceId || 'FEHLT'));
console.log('Model: ' + modelId);
console.log('Wiki-Suche: AN (default)');
}
/* Set global search flag */
window.helikonSearchEnabled = searchActive;
if (ttsApiKey) {
setStatus('bereit');
} else {
setStatus('buddy.ttsapikey fehlt');
}
updateButtons();
watchChat();
});
})();
NoElements {} |