1/** 2 * AIè¯é³æ¨¡å 3 * æä»¶: js/ai-tts.js 4 */ 5(function() { 6 'use strict'; 7 if (window._aiTtsLoaded) return; 8 window._aiTtsLoaded = true; 9 10 // ===== ææ¾å¨ ===== 11 var aiAudioPlayer = document.createElement('audio'); 12 aiAudioPlayer.id = 'aiAudioPlayer'; 13 aiAudioPlayer.style.display = 'none'; 14 document.body.appendChild(aiAudioPlayer); 15 16 var hiddenAudioPlayer = document.createElement('audio'); 17 hiddenAudioPlayer.id = 'hiddenAudioPlayer'; 18 hiddenAudioPlayer.style.display = 'none'; 19 document.body.appendChild(hiddenAudioPlayer); 20 21 var currentAiButton = null; 22 var currentAbortController = null; 23 24 var API_URL = window.location.protocol + '//' + window.location.host + '/api/ai-tts-cached'; 25 26 var PLAY_ICON = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzRjYWY4NCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBkPSJNMTAgOHY4bDYuNzYtNHoiIGZpbGw9InVybCgjZ3JhZGllbnQpIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudCIgeDE9IjAiIHkxPSIwIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiM0Y2FmODQiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiM4MWM3ODQiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4='; 27 var AUDIO_ICON = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI2Q0MGEwMCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBkPSJNMTAgOHY4bDYuNzYtNHoiIGZpbGw9InVybCgjZ3JhZGllbnQpIi8+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkaWVudCIgeDE9IjAiIHkxPSIwIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9IiNlZDM1MzUiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmNiMDciLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48L3N2Zz4='; 28 29 function cleanText(t){ 30 if (!t) return ''; 31 t = t.trim(); 32 var matches = t.match(/[a-zA-ZÄÄĥĵÅÅÄÄĤĴÅŬ\s'-]+/g); 33 34 if(matches && matches.length>0){ 35 var longest=''; 36 for(var i=0;i<matches.length;i++){ 37 var s=matches[i].trim(); 38 if(s.length>longest.length) longest=s; 39 } 40 var combined=matches.filter(function(m){return m.trim().length>0;}).join(' ').trim(); 41 var result=combined.length>longest.length*1.5?combined:longest; 42 return result.replace(/[.,;:!?\s]+$/,''); 43 } 44 45 return t; 46 } 47 48 // ===== æåä¸å«supæ ç¾çææ¬ ===== 49 function getTextWithoutSup(el) { 50 // ä¸´æ¶æ¸ 空supæ ç¾å 容ï¼è·åææ¬ååæ¢å¤ 51 var sups = el.querySelectorAll('sup'); 52 var backup = []; 53 for (var i = 0; i < sups.length; i++) { 54 backup.push(sups[i].textContent); 55 sups[i].textContent = ''; 56 } 57 var text = el.textContent; 58 // æ¢å¤supå 容 59 for (var i = 0; i < sups.length; i++) { 60 sups[i].textContent = backup[i]; 61 } 62 return text; 63 } 64 65 // ===== å ¨å±å½æ°ï¼åå»ºå¾æ ===== 66 window.createAiPlayIcon = function() { 67 var icon = document.createElement('img'); 68 icon.src = PLAY_ICON; 69 icon.width = 14; icon.height = 14; 70 icon.style.verticalAlign = 'middle'; 71 icon.alt = ''; 72 return icon; 73 }; 74 75 window.createAudioPlayIcon = function() { 76 var icon = document.createElement('img'); 77 icon.src = AUDIO_ICON; 78 icon.width = 18; icon.height = 18; 79 icon.style.verticalAlign = 'middle'; 80 icon.alt = ''; 81 return icon; 82 }; 83 84 // ===== å ¨å±å½æ°ï¼é³é¢ææ¾ ===== 85 window.playAudioWithControls = function(audioUrl) { 86 var p = document.getElementById('hmrbPlayer'); 87 var c = document.getElementById('audioPlayerContainer'); 88 if (!p || !c) return; 89 p.src = audioUrl; 90 c.classList.remove('hidden'); 91 p.controls = true; 92 p.play().catch(function() {}); 93 }; 94 95 window.playAudioWithoutControls = function(audioUrl) { 96 hiddenAudioPlayer.src = audioUrl; 97 hiddenAudioPlayer.play().catch(function() {}); 98 }; 99 100 window.closeAudioPlayer = function() { 101 var p = document.getElementById('hmrbPlayer'); 102 var c = document.getElementById('audioPlayerContainer'); 103 if (p) { p.pause(); p.src = ''; } 104 if (c) c.classList.add('hidden'); 105 }; 106 107 window.initAudioPlayer = function() { 108 if (!document.getElementById('audioPlayerContainer')) { 109 var c = document.createElement('div'); 110 c.id = 'audioPlayerContainer'; 111 c.className = 'audio-player-container hidden'; 112 c.innerHTML = '<div class="audio-player"><audio id="hmrbPlayer" controls></audio><button class="close-audio-btn" onclick="closeAudioPlayer()">×</button></div>'; 113 document.body.appendChild(c); 114 } 115 document.addEventListener('click', function(e) { 116 var t = e.target; 117 if (t.tagName === 'A') { 118 var h = t.getAttribute('href'); 119 if (h && h.toLowerCase().endsWith('.mp3')) { 120 e.preventDefault(); 121 var w = document.getElementById('webBrowser'); 122 if (w && w.contains(t)) window.playAudioWithControls(h); 123 else window.playAudioWithoutControls(h); 124 } 125 } 126 }); 127 }; 128 129 // ===== å ¨å±å½æ°ï¼éç½®æé® ===== 130 window.resetAiButton = function() { 131 if (currentAbortController) { 132 try { currentAbortController.abort(); } catch(e) {} 133 currentAbortController = null; 134 } 135 if (currentAiButton) { 136 currentAiButton.classList.remove('playing', 'loading'); 137 currentAiButton.title = 'SAIæè¯»ä¾å¥'; 138 currentAiButton = null; 139 }
140 aiAudioPlayer.onended = aiAudioPlayer.onerror = aiAudioPlayer.oncanplaythrough = null; 141 try { aiAudioPlayer.pause(); } catch(e) {} 142 if (aiAudioPlayer.src) { 143 URL.revokeObjectURL(aiAudioPlayer.src); 144 aiAudioPlayer.removeAttribute('src'); 145 } 146 }; 147 148 // ===== å ¨å±å½æ°ï¼è°ç¨AIè¯é³ ===== 149 window.callAiTts = async function(text, button) { 150 var abortController = new AbortController(); 151 currentAbortController = abortController; 152 153 try { 154 aiAudioPlayer.pause(); 155 if (aiAudioPlayer.src) { 156 URL.revokeObjectURL(aiAudioPlayer.src); 157 aiAudioPlayer.removeAttribute('src'); 158 } 159 if (currentAiButton && currentAiButton !== button) { 160 currentAiButton.classList.remove('playing', 'loading'); 161 currentAiButton.title = 'SAIæè¯»ä¾å¥'; 162 } 163 currentAiButton = button; 164 button.classList.add('loading'); 165 button.title = 'æ£å¨è·åSAIè¯é³...'; 166 167 var clean = cleanText(text); 168 var response = await fetch(API_URL, { 169 method: 'POST', 170 headers: { 'Content-Type': 'application/json' }, 171 body: JSON.stringify({ text: clean }), 172 signal: abortController.signal 173 }); 174 175 if (!response.ok) { 176 var em = 'SAIè¯é³æå¡ææ¶ä¸å¯ç¨'; 177 try { var d = await response.json(); em = d.error || em; } catch(e) {} 178 throw new Error(em); 179 } 180 181 var blob = await response.blob(); 182 if (!blob.size) throw new Error('空æä»¶'); 183 if (abortController.signal.aborted) return; 184 185 if (aiAudioPlayer.src) { URL.revokeObjectURL(aiAudioPlayer.src); aiAudioPlayer.removeAttribute('src'); } 186 var url = URL.createObjectURL(blob); 187 aiAudioPlayer.src = url; 188 189 await new Promise(function(resolve, reject) { 190 var t = setTimeout(function() { reject(new Error('å è½½è¶ æ¶')); }, 15000); 191 aiAudioPlayer.oncanplaythrough = function() { clearTimeout(t); resolve(); }; 192 aiAudioPlayer.onerror = function() { clearTimeout(t); reject(new Error('å 载失败')); }; 193 }); 194 195 if (abortController.signal.aborted) { URL.revokeObjectURL(url); return; } 196 197 button.classList.remove('loading'); 198 button.classList.add('playing'); 199 button.title = 'æ£å¨ææ¾...'; 200 201 await aiAudioPlayer.play(); 202 await new Promise(function(resolve) { 203 aiAudioPlayer.onended = function() { aiAudioPlayer.onended = aiAudioPlayer.onerror = null; resolve(); }; 204 aiAudioPlayer.onerror = function() { aiAudioPlayer.onerror = aiAudioPlayer.onended = null; resolve(); }; 205 }); 206 207 button.classList.remove('playing', 'loading'); 208 button.title = 'SAIæè¯»ä¾å¥'; 209 210 } catch (e) { 211 if (e.name === 'AbortError') return; 212 if (button) { 213 button.classList.remove('playing', 'loading'); 214 button.title = 'SAIæè¯»ä¾å¥ï¼ç¹å»éè¯ï¼'; 215 } 216 } finally { 217 if (aiAudioPlayer.src) { URL.revokeObjectURL(aiAudioPlayer.src); aiAudioPlayer.removeAttribute('src'); } 218 currentAbortController = null; 219 if (currentAiButton === button) currentAiButton = null; 220 } 221 }; 222 223 // ===== å ¨å±å½æ°ï¼æ·»å AIæé® ===== 224 window.addAiSpeakButtons = function() { 225 var wb = document.getElementById('webBrowser'); 226 if (!wb) return; 227 var ckz = wb.querySelectorAll('strong.ckz'); 228 ckz.forEach(function(c) { 229 var n = c.nextElementSibling; 230 if (n && n.classList.contains('ai-speak-btn')) return; 231 232 // è·åä¸å«supæ ç¾çææ¬ 233 var rawText = getTextWithoutSup(c); 234 var t = cleanText(rawText); 235 if (!t || t.length < 2) return; 236 237 var b = document.createElement('span'); 238 b.className = 'ai-speak-btn'; 239 b.title = 'SAIæè¯»ä¾å¥'; 240 b.setAttribute('data-ckz-text', t); 241 b.appendChild(window.createAiPlayIcon()); 242 b.onclick = function(e) { 243 e.preventDefault(); e.stopPropagation(); 244 var s = this; 245 if (s.classList.contains('playing')) { 246 aiAudioPlayer.pause(); 247 if (aiAudioPlayer.src) { URL.revokeObjectURL(aiAudioPlayer.src); aiAudioPlayer.removeAttribute('src'); } 248 window.resetAiButton(); 249 return; 250 } 251 if (s.classList.contains('loading')) { 252 if (currentAbortController) { currentAbortController.abort(); currentAbortController = null; } 253 window.resetAiButton(); 254 return; 255 } 256 aiAudioPlayer.pause(); 257 if (aiAudioPlayer.src) { URL.revokeObjectURL(aiAudioPlayer.src); aiAudioPlayer.removeAttribute('src'); } 258 document.querySelectorAll('.ai-speak-btn.playing, .ai-speak-btn.loading').forEach(function(x) { 259 x.classList.remove('playing', 'loading'); x.title = 'SAIæè¯»ä¾å¥'; 260 }); 261 window.callAiTts(s.getAttribute('data-ckz-text'), s); 262 }; 263 c.insertAdjacentElement('afterend', b); 264 }); 265 }; 266 267})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.