Sprache

Von $1

    Inhaltsverzeichnis
    keine Gliederung

    Version seit 18:55, 11 Aug 2026

    zu dieser Version.

    Zurück zu Versionshistorie.

    Zeige aktuelle Version

     
    (function() {
    
        /* === INJECT CSS === */
        var css = ''
        + '#helikonTTS { max-width:700px; margin:0 auto 20px auto; font-family:"Courier New",Courier,monospace; }'
        + '#ttsControls { display:flex; align-items:center; gap:12px; padding:8px 12px; background:#1a1a2e; border:1px solid #333; border-radius:8px; }'
        + '#ttsToggle { background:none; border:2px solid #555; color:#999; padding:6px 14px; border-radius:6px; cursor:pointer; font-family:inherit; font-size:13px; display:flex; align-items:center; gap:6px; transition:all 0.3s ease; }'
        + '#ttsToggle:hover { border-color:#888; color:#ccc; }'
        + '#ttsToggle.active { border-color:#4CAF50; color:#4CAF50; background:rgba(76,175,80,0.1); }'
        + '#ttsIcon { font-size:16px; }'
        + '#ttsLabel { font-size:12px; }'
        + '#ttsStatus { color:#666; font-size:11px; flex:1; text-align:right; }'
        + '.tts-play-btn { background:none; border:1px solid #555; color:#888; padding:2px 8px; border-radius:4px; cursor:pointer; font-size:11px; margin-left:8px; transition:all 0.2s ease; font-family:"Courier New",Courier,monospace; }'
        + '.tts-play-btn:hover { border-color:#4CAF50; color:#4CAF50; }'
        + '.tts-play-btn.playing { border-color:#ff9800; color:#ff9800; }'
        + '.tts-play-btn:disabled { opacity:0.4; cursor:not-allowed; }';
    
        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 ttsIcon   = document.getElementById('ttsIcon');
        var ttsLabel  = document.getElementById('ttsLabel');
        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) {
                                    /* fetch value sync-ish via nested XHR */
                                    props['_href_' + short] = href;
                                }
                                var textNode = contentsEl[0].textContent;
                                if (textNode) {
                                    props[short] = textNode.replace(/^\s+|\s+$/g, '');
                                }
                            }
                        }
                    } catch(e) { /* parse error */ }
                }
                /* Now 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();
        }
    
        /* === TTS API CALL === */
        function speak(text, onDone) {
            if (!ttsApiKey || !voiceId) {
                setStatus('TTS nicht konfiguriert');
                if (onDone) onDone();
                return;
            }
    
            /* Strip markdown/HTML for cleaner speech */
            var clean = text
                .replace(/<[^>]*>/g, '')
                .replace(/\*\*([^*]+)\*\*/g, '$1')
                .replace(/\*([^*]+)\*/g, '$1')
                .replace(/`([^`]+)`/g, '$1')
                .replace(/#{1,6}\s/g, '')
                .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
                .replace(/\s+/g, ' ')
                .replace(/^\s+|\s+$/g, '');
    
            if (!clean) {
                if (onDone) onDone();
                return;
            }
    
            /* Truncate very long texts (ElevenLabs has limits) */
            if (clean.length > 5000) {
                clean = clean.substring(0, 5000);
            }
    
            setStatus('spreche...');
            isSpeaking = true;
    
            var url = 'https://api.elevenlabs.io/v1/text-to-speech/' + encodeURIComponent(voiceId)
                    + '?output_format=mp3_44100_128';
    
            var body = JSON.stringify(new Object());
            /* Build body manually to avoid MindTouch object literal issues */
            var bodyObj = new Object();
            bodyObj.text = clean;
            bodyObj.model_id = modelId;
            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 (xhr.status === 200) {
                    var blob = xhr.response;
                    var audioUrl = URL.createObjectURL(blob);
    
                    /* Stop previous audio if playing */
                    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();
                } else {
                    isSpeaking = false;
                    var errMsg = 'TTS-Fehler ' + xhr.status;
                    if (xhr.status === 401) errMsg = 'TTS-Key ungueltig';
                    if (xhr.status === 429) errMsg = 'TTS Rate-Limit erreicht';
                    setStatus(errMsg);
                    if (onDone) onDone();
                }
            };
    
            xhr.onerror = function() {
                isSpeaking = false;
                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() {
            if (autoplay) {
                toggleBtn.className = 'active';
                ttsIcon.innerHTML = '��';
                ttsLabel.textContent = 'Sprache: An';
            } else {
                toggleBtn.className = '';
                ttsIcon.innerHTML = '��';
                ttsLabel.textContent = 'Sprache: Aus';
            }
        }
    
        /* === ADD PLAY BUTTON TO BOT MESSAGE === */
        function addPlayButton(msgEl) {
            var textEl = msgEl.querySelector('.msg-text');
            if (!textEl) {
                /* fallback: use the element itself */
                textEl = msgEl;
            }
    
            var playBtn = document.createElement('button');
            playBtn.className = 'tts-play-btn';
            playBtn.innerHTML = '▶ vorlesen';
            playBtn.title = 'Nachricht vorlesen';
    
            playBtn.addEventListener('click', function() {
                if (isSpeaking && playBtn.className.indexOf('playing') !== -1) {
                    stopAudio();
                    playBtn.innerHTML = '▶ vorlesen';
                    playBtn.className = 'tts-play-btn';
                    setStatus('gestoppt');
                    return;
                }
    
                /* Stop any other playing audio */
                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 = textEl.textContent || textEl.innerText || '';
                speak(rawText, function() {
                    playBtn.innerHTML = '▶ vorlesen';
                    playBtn.className = 'tts-play-btn';
                });
            });
    
            msgEl.appendChild(playBtn);
        }
    
        /* === WATCH CHAT FOR NEW BOT MESSAGES === */
        function watchChat() {
            var chatLog = document.getElementById('chatLog');
            if (!chatLog) {
                /* Retry - chat template might load after TTS template */
                setTimeout(watchChat, 500);
                return;
            }
    
            /* Add play buttons to existing bot messages */
            var existing = chatLog.querySelectorAll('.msg-bot');
            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;
    
                            /* Check if it's a bot message */
                            var isBotMsg = false;
                            if (node.className && node.className.indexOf('msg-bot') !== -1) {
                                isBotMsg = true;
                            }
    
                            if (isBotMsg) {
                                /* Add play button */
                                if (!node.querySelector('.tts-play-btn')) {
                                    addPlayButton(node);
                                }
    
                                /* Autoplay if enabled */
                                if (autoplay && ttsApiKey && voiceId) {
                                    var textNode = node.querySelector('.msg-text');
                                    var msgText = textNode ? (textNode.textContent || textNode.innerText) : (node.textContent || node.innerText);
                                    if (msgText) {
                                        speak(msgText.replace(/^\s+|\s+$/g, ''));
                                    }
                                }
                            }
                        }
                    }
                });
    
                var obsConfig = new Object();
                obsConfig.childList = true;
                obsConfig.subtree = false;
                observer.observe(chatLog, obsConfig);
            }
        }
    
        /* === TOGGLE HANDLER === */
        toggleBtn.addEventListener('click', function() {
            if (!ttsApiKey || !voiceId) {
                setStatus('TTS nicht konfiguriert! buddy.ttsapikey / buddy.ttsvoiceid fehlen');
                return;
            }
            autoplay = !autoplay;
            updateToggleUI();
            if (autoplay) {
                setStatus('Autoplay aktiv');
            } else {
                stopAudio();
                setStatus('Autoplay deaktiviert');
            }
        });
    
        /* === INIT === */
        setStatus('lade TTS-Konfiguration...');
    
        fetchAllProperties(function(props) {
            ttsApiKey = props['buddy.ttsapikey'] || '';
            voiceId   = props['buddy.ttsvoiceid'] || '';
            modelId   = props['buddy.ttsmodel'] || 'eleven_multilingual_v2';
    
            var keyOk   = ttsApiKey ? 'OK' : 'FEHLT';
            var voiceOk = voiceId ? 'OK' : 'FEHLT';
            setStatus('TTS-Key: ' + keyOk + ' | Voice: ' + voiceOk);
    
            if (ttsApiKey && voiceId) {
                setStatus('bereit — Sprache aus (klick zum aktivieren)');
            } else {
                setStatus('buddy.ttsapikey oder buddy.ttsvoiceid fehlt');
            }
    
            /* Start watching chat */
            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 :-)