Sprache

Von $1

    Inhaltsverzeichnis
    keine Gliederung

    Version seit 18:55, 11 Aug 2026

    zu dieser Version.

    Zurück zu Versionshistorie.

    Zeige aktuelle Version

    ○ Sprache: Aus  
    (function() {
    
        /* === INJECT CSS === */
        var css = ''
        + '#helikonTTS { max-width:700px; margin:0; font-family:"Courier New",Courier,monospace; }'
        + '#ttsControls { display:flex; align-items:center; gap:10px; padding:0; }'
        + '#ttsToggle { background:#fff; border:2px solid #0077aa; color:#0077aa; padding:10px 20px; cursor:pointer; font-family:inherit; font-size:14px; display:inline-block; border-radius:4px; }'
        + '#ttsToggle:hover { background:#0077aa; color:#fff; }'
        + '#ttsToggle.active { background:#0077aa; color:#fff; }'
        + '#ttsStatus { color:#999; font-size:11px; margin-left:8px; }'
        + '.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-play-btn:disabled { opacity:0.4; cursor:default; }'
        + '.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 toggleBtn = document.getElementById('ttsToggle');
        var ttsStatus = document.getElementById('ttsStatus');
        if (!toggleBtn) return;
    
        /* === STATE === */
        var autoplay  = false;
        var ttsApiKey = '';
        var voiceId   = '';
        var modelId   = 'eleven_multilingual_v2';
        var currentAudio = null;
        var isSpeaking = false;
    
        /* === 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) { /* parse error */ }
                }
                /* Fetch actual values from href endpoints */
                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 propHref = props['_href_' + propName];
                        var xh = new XMLHttpRequest();
                        xh.open('GET', propHref, 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();
        }
    
        /* === CLEAN TEXT FOR SPEECH === */
        function cleanText(text) {
            /* Build regex patterns via new RegExp to avoid MindTouch HTML parser issues */
            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, '');
    
            /* Remove the > prefix from buddy messages */
            if (clean.indexOf('>') === 0) {
                clean = clean.substring(1).replace(/^\s+/, '');
            }
    
            return clean;
        }
    
        /* === TTS API CALL === */
        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;
            }
    
            /* Truncate very long texts */
            if (clean.length > 5000) {
                clean = clean.substring(0, 5000);
            }
    
            if (typeof console !== 'undefined') {
                console.log('TTS speak: "' + clean.substring(0, 80) + '..." (' + 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 body = JSON.stringify(bodyObj);
    
            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 (typeof console !== 'undefined') {
                    console.log('TTS API response: ' + xhr.status + ' | size: ' + (xhr.response ? xhr.response.size : 0) + ' bytes');
                }
                if (xhr.status === 200) {
                    var blob = xhr.response;
                    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(e) {
                        isSpeaking = false;
                        if (typeof console !== 'undefined') {
                            console.log('TTS Audio playback error:', e);
                        }
                        setStatus('Wiedergabefehler');
                        URL.revokeObjectURL(audioUrl);
                        currentAudio = null;
                        if (onDone) onDone();
                    };
                    currentAudio.play().then(function() {
                        if (typeof console !== 'undefined') {
                            console.log('TTS Audio playing!');
                        }
                    }).catch(function(err) {
                        if (typeof console !== 'undefined') {
                            console.log('TTS Audio play() rejected:', err);
                        }
                        setStatus('Autoplay blockiert — klick vorlesen');
                        isSpeaking = false;
                        if (onDone) onDone();
                    });
                } else {
                    isSpeaking = false;
                    var errMsg = 'TTS-Fehler ' + xhr.status;
                    if (xhr.status === 401) errMsg = 'TTS-Key ungueltig (401)';
                    if (xhr.status === 403) errMsg = 'TTS-Key gesperrt (403)';
                    if (xhr.status === 429) errMsg = 'TTS Rate-Limit (429)';
                    if (typeof console !== 'undefined') {
                        /* Try to read error body */
                        try {
                            var reader = new FileReader();
                            reader.onload = function() {
                                console.log('TTS Error Body: ' + reader.result);
                            };
                            reader.readAsText(xhr.response);
                        } catch(ignore) {}
                    }
                    setStatus(errMsg);
                    if (onDone) onDone();
                }
            };
    
            xhr.onerror = function() {
                isSpeaking = false;
                if (typeof console !== 'undefined') {
                    console.log('TTS XHR network error');
                }
                setStatus('TTS nicht erreichbar');
                if (onDone) onDone();
            };
    
            xhr.send(body);
        }
    
        function stopAudio() {
            if (currentAudio) {
                currentAudio.pause();
                currentAudio.currentTime = 0;
                currentAudio = null;
            }
            isSpeaking = false;
        }
    
        /* === UI HELPERS === */
        function setStatus(msg) {
            if (ttsStatus) ttsStatus.textContent = msg;
        }
    
        function updateToggleUI() {
            var chatLog = document.getElementById('chatLog');
            if (autoplay) {
                toggleBtn.className = 'active';
                toggleBtn.innerHTML = '● Sprache: An';
                if (chatLog) chatLog.className = chatLog.className.replace(' tts-active', '') + ' tts-active';
            } else {
                toggleBtn.className = '';
                toggleBtn.innerHTML = '○ Sprache: Aus';
                if (chatLog) chatLog.className = chatLog.className.replace(' tts-active', '');
                stopAudio();
            }
        }
    
        /* === GET CLEAN TEXT FROM BUDDY MESSAGE (without play button) === */
        function getBuddyText(el) {
            var clone = el.cloneNode(true);
            /* Remove any play buttons from clone */
            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 || '';
        }
    
        /* === ADD PLAY BUTTON TO BUDDY MESSAGE === */
        function addPlayButton(msgEl) {
            /* Text is directly in the .chatMsg.buddy element */
            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';
    
                var rawText = getBuddyText(msgEl);
                speak(rawText, function() {
                    playBtn.innerHTML = '▶ vorlesen';
                    playBtn.className = 'tts-play-btn';
                });
            };
    
            msgEl.appendChild(playBtn);
        }
    
        /* === CHECK IF ELEMENT IS A BUDDY MESSAGE === */
        function isBuddyMessage(node) {
            if (!node.className) return false;
            var cls = node.className;
            /* Match: chatMsg buddy (but not typing, not user, not system) */
            if (cls.indexOf('chatMsg') !== -1 && cls.indexOf('buddy') !== -1) {
                if (cls.indexOf('typing') !== -1) return false;
                return true;
            }
            return false;
        }
    
        /* === WATCH CHAT FOR NEW BOT MESSAGES === */
        function watchChat() {
            var chatLog = document.getElementById('chatLog');
            if (!chatLog) {
                setTimeout(watchChat, 500);
                return;
            }
    
            if (typeof console !== 'undefined') {
                console.log('TTS: chatLog gefunden, observer startet');
            }
    
            /* Add play buttons to existing buddy messages */
            var existing = chatLog.querySelectorAll('.chatMsg.buddy');
            for (var e = 0; e < existing.length; e++) {
                if (!existing[e].querySelector('.tts-play-btn')) {
                    addPlayButton(existing[e]);
                }
            }
    
            /* Observe for new messages */
            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)) {
                                if (typeof console !== 'undefined') {
                                    console.log('TTS: neue Buddy-Nachricht! class="' + node.className + '"');
                                }
    
                                /* Small delay to let text render fully */
                                (function(el) {
                                    setTimeout(function() {
                                        if (!el.querySelector('.tts-play-btn')) {
                                            addPlayButton(el);
                                        }
    
                                        if (autoplay && ttsApiKey && voiceId) {
                                            var msgText = getBuddyText(el);
                                            if (msgText) {
                                                speak(msgText.replace(/^\s+|\s+$/g, ''));
                                            }
                                        }
                                    }, 200);
                                })(node);
                            }
                        }
                    }
                });
    
                var obsConfig = new Object();
                obsConfig.childList = true;
                obsConfig.subtree = false;
                observer.observe(chatLog, obsConfig);
    
                if (typeof console !== 'undefined') {
                    console.log('TTS: MutationObserver aktiv auf chatLog');
                }
            }
        }
    
        /* === TOGGLE HANDLER === */
        toggleBtn.onclick = function() {
            if (!ttsApiKey || !voiceId) {
                setStatus('buddy.ttsapikey / buddy.ttsvoiceid fehlen!');
                return;
            }
            autoplay = !autoplay;
            updateToggleUI();
            if (autoplay) {
                setStatus('Autoplay aktiv');
            } else {
                stopAudio();
                setStatus('Autoplay deaktiviert');
            }
        };
    
        /* === INIT === */
        setStatus('lade TTS...');
    
        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 TTS v2 INIT ===');
                console.log('TTS Key: ' + (ttsApiKey ? ttsApiKey.substring(0,8) + '...' : 'FEHLT'));
                console.log('Voice ID: ' + (voiceId || 'FEHLT'));
                console.log('Model: ' + modelId);
                console.log('Page ID: ' + pageId);
            }
    
            if (ttsApiKey && voiceId) {
                setStatus('bereit');
            } else {
                var missing = [];
                if (!ttsApiKey) missing.push('buddy.ttsapikey');
                if (!voiceId) missing.push('buddy.ttsvoiceid');
                setStatus(missing.join(' + ') + ' fehlt');
            }
    
            watchChat();
        });
    
        updateToggleUI();
    
    })();
    NoElements {}

     
    Use the language selection below for English Utilise le sélecteur de langue ci-dessous pour français Per l'italiano, utilizzare la selezione della lingua qui sotto Schön sprichst du deutsch :-)