|
|
|
SpracheVon $1Inhaltsverzeichniskeine GliederungCredits OK Text→Sprache Sprache→Sprache Sprache→Text ☎ Helikon anrufen Wiki-Suche Stimme: Stimme 1 Stimme 2 ● Aufnehmen 0s (function() {
/* ============================================================
* HELIKON VOICE v6.1 — 2026-02-27
* Light default + dark-mode, CSS pixel toggles, credit via
* TTS error codes only (no subscription API).
* ============================================================ */
var css = ''
/* === CONTAINER — LIGHT DEFAULT === */
+ '#helikonTTS { max-width:700px; margin:0; font-family:"Press Start 2P","Courier New",Courier,monospace; border:2px solid #ccc; border-radius:8px; background:#f8f8f8; padding:12px; position:relative; overflow:hidden; }'
/* === WAVE VISUALIZER === */
+ '#ttsWaveWrap { height:36px; margin:0 0 8px 0; overflow:hidden; border-radius:4px; background:#eee; border:1px solid #ddd; }'
+ '#ttsWave { display:flex; align-items:flex-end; justify-content:center; gap:3px; height:100%; padding:4px 8px; }'
+ '.ttsBar { display:inline-block; width:6px; background:#0077aa; border-radius:2px 2px 0 0; height:4px; opacity:0.25; transition:height 0.15s ease, opacity 0.15s ease; }'
+ '#helikonTTS.tts-on .ttsBar { opacity:0.6; }'
/* Wave idle */
+ '@keyframes waveIdle { 0%,100% { height:4px; opacity:0.3; } 50% { height:10px; opacity:0.6; } }'
+ '#helikonTTS.tts-on .ttsBar { animation:waveIdle 1.5s ease-in-out infinite; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(1) { animation-delay:0s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(2) { animation-delay:0.1s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(3) { animation-delay:0.2s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(4) { animation-delay:0.3s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(5) { animation-delay:0.4s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(6) { animation-delay:0.5s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(7) { animation-delay:0.6s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(8) { animation-delay:0.7s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(9) { animation-delay:0.6s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(10) { animation-delay:0.5s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(11) { animation-delay:0.4s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(12) { animation-delay:0.3s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(13) { animation-delay:0.2s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(14) { animation-delay:0.1s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(15) { animation-delay:0s; }'
+ '#helikonTTS.tts-on .ttsBar:nth-child(16) { animation-delay:0.1s; }'
/* Wave speaking — dramatic */
+ '@keyframes waveTalk { 0% { height:4px; } 15% { height:26px; } 30% { height:8px; } 45% { height:20px; } 60% { height:6px; } 75% { height:28px; } 90% { height:12px; } 100% { height:4px; } }'
+ '#helikonTTS.tts-speaking .ttsBar { opacity:1; animation:waveTalk 0.8s ease-in-out infinite; background:#0077aa; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(1) { animation-delay:0.05s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(2) { animation-delay:0.1s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(3) { animation-delay:0.15s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(4) { animation-delay:0.2s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(5) { animation-delay:0.25s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(6) { animation-delay:0.3s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(7) { animation-delay:0.35s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(8) { animation-delay:0.4s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(9) { animation-delay:0.35s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(10) { animation-delay:0.3s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(11) { animation-delay:0.25s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(12) { animation-delay:0.2s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(13) { animation-delay:0.15s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(14) { animation-delay:0.1s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(15) { animation-delay:0.05s; }'
+ '#helikonTTS.tts-speaking .ttsBar:nth-child(16) { animation-delay:0s; }'
/* Call mode — green waves */
+ '#helikonTTS.tts-call .ttsBar { opacity:0.6; animation:waveIdle 1.2s ease-in-out infinite; background:#3a8f3a; }'
+ '#helikonTTS.tts-call.tts-speaking .ttsBar { opacity:1; animation:waveTalk 0.8s ease-in-out infinite; background:#3a8f3a; }'
/* === CREDIT LAMP === */
+ '#ttsCreditLamp { position:absolute; top:12px; right:12px; display:flex; align-items:center; gap:6px; z-index:2; }'
+ '#creditDot { display:inline-block; width:10px; height:10px; border-radius:50%; background:#3a8f3a; box-shadow:0 0 4px rgba(58,143,58,0.4); transition:all 0.3s ease; }'
+ '#creditDot.ok { background:#3a8f3a; box-shadow:0 0 4px rgba(58,143,58,0.4); }'
+ '#creditDot.empty { background:#cc3333; box-shadow:0 0 10px rgba(204,51,51,0.7); animation:lampAlarm 0.6s infinite; }'
+ '#creditLabel { font-size:7px; color:#999; letter-spacing:0.5px; text-transform:uppercase; }'
+ '#creditLabel.empty { color:#cc3333; font-weight:bold; }'
+ '@keyframes lampAlarm { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:0.4; transform:scale(1.4); } }'
/* === PIXEL TOGGLE BUTTONS === */
+ '#ttsControls { display:flex; align-items:center; gap:6px; padding:8px 0; flex-wrap:wrap; }'
/* Pixel switch: box with sliding knob */
+ '.voice-btn { background:#fff; border:2px solid #ccc; color:#555; padding:8px 12px; cursor:pointer; font-family:"Press Start 2P","Courier New",Courier,monospace; font-size:9px; display:inline-flex; align-items:center; gap:6px; border-radius:4px; transition:all 0.2s ease; position:relative; }'
+ '.voice-btn:hover { border-color:#0077aa; color:#0077aa; }'
/* Pixel LED indicator in button */
+ '.voice-btn::before { content:""; display:inline-block; width:8px; height:8px; border:2px solid #ccc; background:#eee; border-radius:0; transition:all 0.15s ease; image-rendering:pixelated; }'
+ '.voice-btn.active { background:#e8f4ff; border-color:#0077aa; color:#0077aa; }'
+ '.voice-btn.active::before { background:#0077aa; border-color:#005580; box-shadow:0 0 6px rgba(0,119,170,0.5); }'
+ '.voice-btn.disabled { background:#f5f5f5; border-color:#ddd; color:#bbb; cursor:default; }'
+ '.voice-btn.disabled:hover { border-color:#ddd; color:#bbb; }'
+ '.voice-btn.disabled::before { background:#ddd; border-color:#ccc; }'
/* Call button — green */
+ '#callToggle { border-color:#ccc; color:#555; }'
+ '#callToggle::before { border-color:#ccc; background:#eee; }'
+ '#callToggle:hover { border-color:#3a8f3a; color:#3a8f3a; }'
+ '#callToggle.active { background:#e8ffe8; border-color:#3a8f3a; color:#3a8f3a; }'
+ '#callToggle.active::before { background:#3a8f3a; border-color:#2a6f2a; box-shadow:0 0 6px rgba(58,143,58,0.5); }'
+ '@keyframes callGlow { 0%,100% { box-shadow:0 0 6px rgba(58,143,58,0.3); } 50% { box-shadow:0 0 14px rgba(58,143,58,0.6); } }'
+ '#callToggle.active { animation:callGlow 2s infinite; }'
/* Search button — orange */
+ '#searchToggle:hover { border-color:#cc8800; color:#cc8800; }'
+ '#searchToggle.active { background:#fff8e8; border-color:#cc8800; color:#cc8800; }'
+ '#searchToggle.active::before { background:#cc8800; border-color:#aa6600; box-shadow:0 0 6px rgba(204,136,0,0.5); }'
/* === VOICE SELECTOR === */
+ '#ttsVoiceSel { display:flex; align-items:center; gap:6px; padding:6px 0; border-top:1px solid #eee; }'
+ '#voiceLabel { font-size:7px; color:#999; text-transform:uppercase; letter-spacing:0.5px; margin-right:2px; }'
+ '.voice-opt { background:#fff; border:2px solid #ddd; color:#888; padding:6px 10px; cursor:pointer; font-family:"Press Start 2P","Courier New",Courier,monospace; font-size:8px; border-radius:4px; transition:all 0.2s ease; }'
+ '.voice-opt:hover { border-color:#0077aa; color:#0077aa; }'
+ '.voice-opt.active { background:#e8f4ff; border-color:#0077aa; color:#0077aa; }'
/* === STATUS BAR === */
+ '#ttsStatusBar { padding:2px 0; }'
+ '#ttsStatus { color:#999; font-size:7px; letter-spacing:0.5px; text-transform:uppercase; }'
/* === RECORDING BAR === */
+ '#voiceRecBar { display:none; align-items:center; gap:10px; padding:6px 0; border-top:1px solid #eee; }'
+ '#voiceRecBar.visible { display:flex; }'
+ '#voiceRecBtn { background:#fff; border:2px solid #cc3333; color:#cc3333; padding:8px 12px; cursor:pointer; font-family:"Press Start 2P","Courier New",Courier,monospace; font-size:9px; border-radius:4px; transition:all 0.2s ease; }'
+ '#voiceRecBtn:hover { background:#cc3333; color:#fff; }'
+ '#voiceRecBtn.recording { background:#cc3333; color:#fff; animation:recPulse 1s infinite; box-shadow:0 0 10px rgba(204,51,51,0.4); }'
+ '@keyframes recPulse { 0%,100% { opacity:1; } 50% { opacity:0.6; } }'
+ '#voiceRecTime { color:#cc3333; font-size:9px; min-width:30px; }'
+ '#voiceRecStatus { color:#999; font-size:7px; }'
/* === CALL BAR === */
+ '#callBar { display:none; align-items:center; gap:10px; padding:6px 0; border-top:1px solid #eee; }'
+ '#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:#999; font-size:7px; text-transform:uppercase; letter-spacing:0.5px; }'
/* === PLAY BUTTONS ON MESSAGES === */
+ '.tts-play-btn { background:#fff; border:1px solid #ddd; color:#999; padding:3px 8px; cursor:pointer; font-size:8px; margin-left:8px; font-family:"Press Start 2P","Courier New",Courier,monospace; display:none; border-radius:3px; transition:all 0.2s ease; }'
+ '.tts-play-btn:hover { border-color:#0077aa; color:#0077aa; }'
+ '.tts-play-btn.playing { border-color:#0077aa; color:#0077aa; display:inline-block !important; box-shadow:0 0 6px rgba(0,119,170,0.3); }'
+ '.tts-active .tts-play-btn { display:inline-block; }'
/* ============================================================
DARK MODE OVERRIDES — body.dark-mode
============================================================ */
+ 'body.dark-mode #helikonTTS { background:#0a0a1a; border-color:#1a1a3a; }'
+ 'body.dark-mode #ttsWaveWrap { background:#0d0d24; border-color:#1a1a3a; }'
+ 'body.dark-mode .ttsBar { background:#0077aa; }'
+ 'body.dark-mode #helikonTTS.tts-speaking .ttsBar { background:linear-gradient(0deg, #0077aa, #00ccff); }'
+ 'body.dark-mode #helikonTTS.tts-call .ttsBar { background:linear-gradient(0deg, #3a8f3a, #66ff66); }'
+ 'body.dark-mode #creditLabel { color:#666; }'
+ 'body.dark-mode .voice-btn { background:#111; border-color:#333; color:#888; }'
+ 'body.dark-mode .voice-btn:hover { border-color:#0077aa; color:#0077aa; }'
+ 'body.dark-mode .voice-btn::before { background:#222; border-color:#444; }'
+ 'body.dark-mode .voice-btn.active { background:#0a1a2a; border-color:#0077aa; color:#0077aa; }'
+ 'body.dark-mode .voice-btn.active::before { background:#0077aa; border-color:#005580; box-shadow:0 0 8px rgba(0,119,170,0.6); }'
+ 'body.dark-mode .voice-btn.disabled { background:#0a0a0a; border-color:#222; color:#444; }'
+ 'body.dark-mode .voice-btn.disabled::before { background:#222; border-color:#333; }'
+ 'body.dark-mode #callToggle.active { background:#0a1a0a; border-color:#3a8f3a; color:#3a8f3a; }'
+ 'body.dark-mode #callToggle.active::before { background:#3a8f3a; border-color:#2a6f2a; }'
+ 'body.dark-mode #searchToggle.active { background:#1a1500; border-color:#cc8800; color:#cc8800; }'
+ 'body.dark-mode #searchToggle.active::before { background:#cc8800; border-color:#aa6600; }'
+ 'body.dark-mode #ttsVoiceSel { border-color:#1a1a3a; }'
+ 'body.dark-mode .voice-opt { background:#111; border-color:#333; color:#666; }'
+ 'body.dark-mode .voice-opt:hover { border-color:#0077aa; color:#0077aa; }'
+ 'body.dark-mode .voice-opt.active { background:#0a1a2a; border-color:#00ccff; color:#00ccff; }'
+ 'body.dark-mode #ttsStatus { color:#555; }'
+ 'body.dark-mode #voiceRecBar { border-color:#1a1a3a; }'
+ 'body.dark-mode #voiceRecBtn { background:#111; }'
+ 'body.dark-mode #callBar { border-color:#1a1a3a; }'
+ 'body.dark-mode #callStatus { color:#555; }'
+ 'body.dark-mode .tts-play-btn { background:#111; border-color:#333; color:#666; }'
+ 'body.dark-mode .tts-play-btn:hover { border-color:#0077aa; color:#0077aa; }'
+ 'body.dark-mode .tts-play-btn.playing { border-color:#00ccff; color:#00ccff; }';
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);
var fontLink = document.createElement('link');
fontLink.rel = 'stylesheet';
fontLink.href = 'https://fonts.googleapis.com/css2?family=Press+Start+2P';
document.head.appendChild(fontLink);
/* === DOM === */
var ttsWrap = document.getElementById('helikonTTS');
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 ttsStatusEl = 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 callStatusEl = document.getElementById('callStatus');
var creditDot = document.getElementById('creditDot');
var creditLabel = document.getElementById('creditLabel');
var voiceOpt1 = document.getElementById('voiceOpt1');
var voiceOpt2 = document.getElementById('voiceOpt2');
if (!ttsBtn) return;
ttsBtn.className = 'voice-btn';
stsBtn.className = 'voice-btn disabled';
sttBtn.className = 'voice-btn';
callBtn.className = 'voice-btn';
searchBtn.className = 'voice-btn active';
/* === VOICES === */
var VOICES = new Array(2);
VOICES[0] = new Object(); VOICES[0].id = ''; VOICES[0].label = 'Stimme 1';
VOICES[1] = new Object(); VOICES[1].id = 'rKiu7lQ4c5P3az3745s3'; VOICES[1].label = 'Stimme 2';
var activeVoiceIdx = 0;
/* === STATE === */
var ttsActive = false, stsActive = false, sttActive = false, callActive = false;
var searchActive = true;
var ttsApiKey = '', voiceId = '', modelId = 'eleven_multilingual_v2';
var currentAudio = null, isSpeaking = false, isRecording = false;
var mediaRecorder = null, audioChunks = [], recTimer = null, recSeconds = 0;
var creditsOk = true;
var callStream = null, callAnalyser = null, callAudioCtx = null;
var callPhase = 'idle';
var SILENCE_THRESHOLD = 15, SILENCE_DURATION = 1800;
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();
}
/* === CREDIT STATE (driven by TTS errors only) === */
function setCreditState(state) {
if (creditDot) creditDot.className = state;
if (!creditLabel) return;
if (state === 'empty') { creditLabel.textContent = 'Kein Guthaben!'; creditLabel.className = 'empty'; creditsOk = false; }
else { creditLabel.textContent = 'Credits OK'; creditLabel.className = ''; creditsOk = true; }
}
/* === UI HELPERS === */
function setStatus(msg) { if (ttsStatusEl) ttsStatusEl.textContent = msg; }
function setRecStatus(msg) { if (recStatus) recStatus.textContent = msg; }
function setCallStatus(msg) { if (callStatusEl) callStatusEl.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 getActiveVoiceId() { return VOICES[activeVoiceIdx].id || voiceId; }
function updateWrapClass() {
var cls = '';
if (ttsActive || sttActive) cls = 'tts-on';
if (isSpeaking) cls = 'tts-speaking';
if (callActive && !isSpeaking) cls = 'tts-call';
if (callActive && isSpeaking) cls = 'tts-call tts-speaking';
ttsWrap.className = cls;
}
function updateButtons() {
ttsBtn.className = 'voice-btn' + (ttsActive ? ' active' : '');
stsBtn.className = 'voice-btn disabled';
sttBtn.className = 'voice-btn' + (sttActive ? ' active' : '');
callBtn.className = 'voice-btn' + (callActive ? ' active' : '');
searchBtn.className = 'voice-btn' + (searchActive ? ' active' : '');
window.helikonSearchEnabled = searchActive;
if (sttActive) { recBar.className = 'visible'; callBarEl.className = ''; }
else if (callActive) { recBar.className = ''; callBarEl.className = 'visible'; }
else { recBar.className = ''; callBarEl.className = ''; }
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(); }
}
updateWrapClass();
}
/* === VOICE SELECTOR === */
function selectVoice(idx) {
activeVoiceIdx = idx;
voiceOpt1.className = 'voice-opt' + (idx === 0 ? ' active' : '');
voiceOpt2.className = 'voice-opt' + (idx === 1 ? ' active' : '');
setStatus('Stimme: ' + VOICES[idx].label);
}
voiceOpt1.onclick = function() { selectVoice(0); };
voiceOpt2.onclick = function() { selectVoice(1); };
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;
}
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, '');
return clean;
}
/* === TTS === */
function speak(text, onDone) {
var activeId = getActiveVoiceId();
if (!ttsApiKey || !activeId) { setStatus('TTS nicht konfiguriert'); if (onDone) onDone(); return; }
if (!creditsOk) { setStatus('Keine Credits!'); 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 (' + VOICES[activeVoiceIdx].label + '): "' + clean.substring(0, 60) + '..."'); }
setStatus('spreche...'); isSpeaking = true; updateWrapClass();
var url = 'https://api.elevenlabs.io/v1/text-to-speech/' + encodeURIComponent(activeId) + '?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); setCreditState('ok'); }
else if (xhr.status === 401 || xhr.status === 403) { isSpeaking = false; updateWrapClass(); setCreditState('empty'); setStatus('Kein Guthaben!'); if (onDone) onDone(); }
else { isSpeaking = false; updateWrapClass(); setStatus('TTS-Fehler ' + xhr.status); if (onDone) onDone(); }
};
xhr.onerror = function() { isSpeaking = false; updateWrapClass(); setStatus('TTS nicht erreichbar'); if (onDone) onDone(); };
xhr.send(JSON.stringify(bodyObj));
}
function playBlob(blob, onDone) {
var audioUrl = URL.createObjectURL(blob);
stopAudio();
currentAudio = new Audio(audioUrl);
isSpeaking = true; updateWrapClass();
currentAudio.onended = function() { isSpeaking = false; updateWrapClass(); setStatus('bereit'); URL.revokeObjectURL(audioUrl); currentAudio = null; if (onDone) onDone(); };
currentAudio.onerror = function() { isSpeaking = false; updateWrapClass(); setStatus('Wiedergabefehler'); URL.revokeObjectURL(audioUrl); currentAudio = null; if (onDone) onDone(); };
currentAudio.play().catch(function() { setStatus('Autoplay blockiert'); isSpeaking = false; updateWrapClass(); if (onDone) onDone(); });
}
function stopAudio() {
if (currentAudio) { currentAudio.pause(); currentAudio.currentTime = 0; currentAudio = null; }
isSpeaking = false; updateWrapClass();
}
/* === RECORDING === */
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.textContent = '\u25A0 Stopp'; setRecStatus('aufnahme...');
mediaRecorder.ondataavailable = function(e) { if (e.data.size > 0) { audioChunks.push(e.data); } };
mediaRecorder.onstop = function() {
isRecording = false; recBtn.className = ''; recBtn.textContent = '\u25CF 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 bt = new Object(); bt.type = 'audio/webm'; var blob = new Blob(audioChunks, bt); 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; } }
/* === 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 r = JSON.parse(xhr.responseText); callback(r.text || ''); } catch(e) { callback(''); } }
else { setRecStatus('STT-Fehler ' + xhr.status); callback(''); }
};
xhr.onerror = function() { setRecStatus('STT nicht erreichbar'); callback(''); };
xhr.send(formData);
}
/* === STS (disabled) === */
function transformVoice(blob) {
setRecStatus('transformiere...'); 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(getActiveVoiceId()) + '?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) { playBlob(xhr.response, function() { setRecStatus('bereit'); }); } else { setRecStatus('STS-Fehler ' + xhr.status); } };
xhr.onerror = function() { setRecStatus('STS nicht erreichbar'); }; xhr.send(formData);
}
/* === CALL MODE === */
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;
callAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
var source = callAudioCtx.createMediaStreamSource(stream);
callAnalyser = callAudioCtx.createAnalyser(); callAnalyser.fftSize = 512; source.connect(callAnalyser);
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);
var silenceStart = null, hasSpoken = false;
function checkVolume() {
if (!callActive || callPhase !== 'listening') return;
var dataArray = new Uint8Array(callAnalyser.frequencyBinCount); callAnalyser.getByteFrequencyData(dataArray);
var sum = 0; for (var vi = 0; vi < dataArray.length; vi++) { sum += dataArray[vi]; } var avg = sum / dataArray.length;
if (avg > SILENCE_THRESHOLD) { hasSpoken = true; silenceStart = null; }
else if (hasSpoken) { if (!silenceStart) { silenceStart = Date.now(); } else if (Date.now() - silenceStart > SILENCE_DURATION) { callStopListening(); return; } }
if (callActive && callPhase === 'listening') { setTimeout(checkVolume, 100); }
}
setTimeout(checkVolume, 500);
}).catch(function() { setCallStatus('Mikrofon verweigert'); callActive = false; updateButtons(); });
}
function callStopListening() {
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; }
setTimeout(function() {
if (audioChunks.length === 0) { if (callActive) { callStartListening(); } return; }
setCallPhase('processing');
var bt = new Object(); bt.type = 'audio/webm'; var blob = new Blob(audioChunks, bt);
transcribeAudio(blob, function(text) {
if (!text || !callActive) { if (callActive) { callStartListening(); } return; }
var chatInput = document.getElementById('chatInput'); var chatSend = document.getElementById('chatSend');
if (chatInput && chatSend) { chatInput.value = text; setCallPhase('waiting'); chatSend.click(); }
});
}, 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 === */
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.textContent = '\u25B6 vorlesen'; playBtn.title = 'Nachricht vorlesen';
playBtn.onclick = function() {
if (isSpeaking && playBtn.className.indexOf('playing') !== -1) { stopAudio(); playBtn.textContent = '\u25B6 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].textContent = '\u25B6 vorlesen'; allBtns[b].className = 'tts-play-btn'; }
playBtn.textContent = '\u25A0 stopp'; playBtn.className = 'tts-play-btn playing';
speak(getBuddyText(msgEl), function() { playBtn.textContent = '\u25B6 vorlesen'; playBtn.className = 'tts-play-btn'; });
};
msgEl.appendChild(playBtn);
}
function isBuddyMessage(node) {
if (!node.className) return false;
var cls = node.className;
return (cls.indexOf('chatMsg') !== -1 && cls.indexOf('buddy') !== -1 && cls.indexOf('typing') === -1);
}
/* === CHAT OBSERVER === */
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, '');
if (callActive && (callPhase === 'waiting' || callPhase === 'processing')) {
if (msgText) { setCallPhase('speaking'); speak(msgText, function() { if (callActive) { setTimeout(function() { if (callActive) { callStartListening(); } }, 400); } }); }
else { if (callActive) { callStartListening(); } }
return;
}
if (ttsActive && ttsApiKey && getActiveVoiceId()) { 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 || !getActiveVoiceId()) { setStatus('API-Key/Voice fehlt!'); return; } if (callActive) return; ttsActive = !ttsActive; updateButtons(); setStatus(ttsActive ? 'TTS aktiv' : 'TTS aus'); };
stsBtn.onclick = function() { setStatus('STS deaktiviert'); };
sttBtn.onclick = function() { if (!ttsApiKey) { setStatus('API-Key fehlt!'); return; } if (callActive) return; sttActive = !sttActive; updateButtons(); setRecStatus(sttActive ? 'bereit' : ''); 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 || !getActiveVoiceId()) { setStatus('API-Key/Voice fehlt!'); return; }
if (callActive) { callEndAll(); addChatMsg('[Anruf beendet]', 'system'); return; }
callActive = true; ttsActive = false; sttActive = false; stsActive = false;
updateButtons(); addChatMsg('[Anruf gestartet — sprich jetzt...]', 'system'); setStatus('Anruf aktiv'); callStartListening();
};
recBtn.onclick = function() {
if (isRecording) { stopRecording(); return; }
if (sttActive) {
startRecording(function(blob) {
setRecStatus('transkribiere...');
transcribeAudio(blob, function(text) {
if (text) { var ci = document.getElementById('chatInput'); if (ci) { ci.value = text; ci.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';
VOICES[0].id = voiceId;
if (typeof console !== 'undefined') {
console.log('=== HELIKON VOICE v6.1 ===');
console.log('Key: ' + (ttsApiKey ? ttsApiKey.substring(0,8) + '...' : 'FEHLT'));
console.log('Voice 1: ' + (voiceId || 'FEHLT'));
console.log('Voice 2: ' + VOICES[1].id);
console.log('Model: ' + modelId);
}
window.helikonSearchEnabled = searchActive;
if (ttsApiKey) { setStatus('bereit'); }
else { setStatus('buddy.ttsapikey fehlt'); }
updateButtons(); selectVoice(0); watchChat();
});
})();
NoElements {}
|