Sprache

Von $1

    Inhaltsverzeichnis
    keine Gliederung

    Version seit 18:55, 11 Aug 2026

    zu dieser Version.

    Zurück zu Versionshistorie.

    Zeige aktuelle Version

    ○ Text→Sprache ○ Sprache→Sprache ○ Sprache→Text  
    ● 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; }'
        + '#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; }'
        + '.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 ttsStatus  = document.getElementById('ttsStatus');
        var recBar     = document.getElementById('voiceRecBar');
        var recBtn     = document.getElementById('voiceRecBtn');
        var recTime    = document.getElementById('voiceRecTime');
        var recStatus  = document.getElementById('voiceRecStatus');
        if (!ttsBtn) return;
    
        /* Apply button class */
        ttsBtn.className = 'voice-btn';
        stsBtn.className = 'voice-btn';
        sttBtn.className = 'voice-btn';
    
        /* === STATE === */
        var ttsActive  = false;
        var stsActive  = false;
        var sttActive  = false;
        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;
    
        /* === 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 updateButtons() {
            ttsBtn.className = 'voice-btn' + (ttsActive ? ' active' : '');
            ttsBtn.innerHTML = (ttsActive ? '●' : '○') + ' Text→Sprache';
            stsBtn.className = 'voice-btn' + (stsActive ? ' active' : '');
            stsBtn.innerHTML = (stsActive ? '●' : '○') + ' Sprache→Sprache';
            sttBtn.className = 'voice-btn' + (sttActive ? ' active' : '');
            sttBtn.innerHTML = (sttActive ? '●' : '○') + ' Sprache→Text';
    
            /* Show record bar if STS or STT active */
            if (stsActive || sttActive) {
                recBar.className = 'visible';
            } else {
                recBar.className = '';
                stopRecording();
            }
    
            /* TTS play buttons visibility */
            var chatLog = document.getElementById('chatLog');
            if (chatLog) {
                if (ttsActive) {
                    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
           ============================================================ */
        function startRecording(onComplete) {
            if (isRecording) return;
            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                setRecStatus('Mikrofon nicht verfuegbar');
                return;
            }
    
            audioChunks = [];
            recSeconds = 0;
            recTime.textContent = '0s';
    
            /* Build constraints object for getUserMedia */
            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;
    
                    /* Stop all tracks */
                    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();
    
                /* Timer */
                recTimer = setInterval(function() {
                    recSeconds++;
                    recTime.textContent = recSeconds + 's';
                    /* Max 30 seconds */
                    if (recSeconds >= 30) {
                        stopRecording();
                    }
                }, 1000);
    
            }).catch(function(err) {
                setRecStatus('Mikrofon verweigert');
                if (typeof console !== 'undefined') {
                    console.log('Mic error:', err);
                }
            });
        }
    
        function stopRecording() {
            if (mediaRecorder && isRecording) {
                mediaRecorder.stop();
            }
            if (recTimer) {
                clearInterval(recTimer);
                recTimer = null;
            }
        }
    
        /* ============================================================
           SPEECH TO TEXT (STT)
           ============================================================ */
        function transcribeAudio(blob) {
            setRecStatus('transkribiere...');
    
            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 result: "' + text + '"');
                        }
                        if (text) {
                            /* Put text in chat input */
                            var chatInput = document.getElementById('chatInput');
                            if (chatInput) {
                                chatInput.value = text;
                                chatInput.focus();
                            }
                            /* Show in chat as system message */
                            addChatMsg('[STT] ' + text, 'system');
                            setRecStatus('transkribiert: ' + text.substring(0, 40) + (text.length > 40 ? '...' : ''));
                        } else {
                            setRecStatus('kein Text erkannt');
                        }
                    } catch(e) {
                        setRecStatus('STT Parse-Fehler');
                    }
                } else {
                    setRecStatus('STT-Fehler ' + xhr.status);
                    if (typeof console !== 'undefined') {
                        console.log('STT error:', xhr.status, xhr.responseText);
                    }
                }
            };
            xhr.onerror = function() {
                setRecStatus('STT nicht erreichbar');
            };
            xhr.send(formData);
        }
    
        /* ============================================================
           SPEECH TO SPEECH (STS)
           ============================================================ */
        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) {
                    if (typeof console !== 'undefined') {
                        console.log('STS response: ' + xhr.response.size + ' bytes');
                    }
                    addChatMsg('[STS] Stimme transformiert — wird abgespielt...', 'system');
                    setRecStatus('spiele ab...');
                    playBlob(xhr.response, function() {
                        setRecStatus('bereit');
                    });
                } else {
                    setRecStatus('STS-Fehler ' + xhr.status);
                    if (typeof console !== 'undefined') {
                        console.log('STS error:', xhr.status);
                        try {
                            var reader = new FileReader();
                            reader.onload = function() { console.log('STS Error:', reader.result); };
                            reader.readAsText(xhr.response);
                        } catch(ignore) {}
                    }
                }
            };
            xhr.onerror = function() {
                setRecStatus('STS nicht erreichbar');
            };
            xhr.send(formData);
        }
    
        /* ============================================================
           PLAY BUTTONS ON BUDDY MESSAGES (TTS)
           ============================================================ */
        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
           ============================================================ */
        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); }
                                        if (ttsActive && 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);
            }
        }
    
        /* ============================================================
           TOGGLE HANDLERS
           ============================================================ */
        ttsBtn.onclick = function() {
            if (!ttsApiKey || !voiceId) { setStatus('API-Key/Voice fehlt!'); return; }
            ttsActive = !ttsActive;
            updateButtons();
            setStatus(ttsActive ? 'TTS aktiv' : 'TTS aus');
        };
    
        stsBtn.onclick = function() {
            if (!ttsApiKey || !voiceId) { setStatus('API-Key/Voice fehlt!'); return; }
            stsActive = !stsActive;
            if (stsActive) sttActive = false; /* Only one mic mode at a time */
            updateButtons();
            setRecStatus(stsActive ? 'bereit — klick Aufnehmen' : '');
            setStatus(stsActive ? 'STS aktiv' : 'STS aus');
        };
    
        sttBtn.onclick = function() {
            if (!ttsApiKey) { setStatus('API-Key fehlt!'); return; }
            sttActive = !sttActive;
            if (sttActive) stsActive = false; /* Only one mic mode at a time */
            updateButtons();
            setRecStatus(sttActive ? 'bereit — klick Aufnehmen' : '');
            setStatus(sttActive ? 'STT aktiv' : 'STT aus');
        };
    
        /* === RECORD BUTTON === */
        recBtn.onclick = function() {
            if (isRecording) {
                stopRecording();
                return;
            }
    
            if (sttActive) {
                startRecording(function(blob) {
                    transcribeAudio(blob);
                });
            } else if (stsActive) {
                startRecording(function(blob) {
                    transformVoice(blob);
                });
            }
        };
    
        /* ============================================================
           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 v3 ===');
                console.log('Key: ' + (ttsApiKey ? ttsApiKey.substring(0,8) + '...' : 'FEHLT'));
                console.log('Voice: ' + (voiceId || 'FEHLT'));
                console.log('Model: ' + modelId);
            }
    
            if (ttsApiKey) {
                setStatus('bereit');
            } else {
                setStatus('buddy.ttsapikey fehlt');
            }
    
            updateButtons();
            watchChat();
        });
    
    })();
    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 :-)