1/** 2 * Smart-KF æºè½å®¢æç»ä»¶ v16.0 3 * â ï¸â ï¸â ï¸ ç产ç¦åºï¼å ¨ç«å®¢æ UI ç»ä»¶ â ï¸â ï¸â ï¸ 4 * å ¨ç« 163 ç«ç»ä¸ä» img.mobiletrain.org/widget_v16.js å è½½ï¼å䏿ºï¼2026-08-16 èµ·ï¼ 5 * æ¹æ¤æä»¶åå¿ é¡»: â åæ¥å° /data/www/cmstop/public/img/widget_v16.js 6 * â¡ æ¹ smart-kf-global.js ç ?v= çæ¬å· â¢ å· CDN 7 * â 严ç¦: ç´æ¥æ¹åç«ç®å½ä¸ç坿¬ï¼å·²æ¸ çï¼åªæ img ä¸ä»½å¨æå¡ï¼ 8 9 10 * 11 * v21.8 æ´æ°: 主å¨å¼¹åºéé¢ â å3次10ç§ä¸å / 4-6次60ç§ / 7-9次120ç§ / 10次起300ç§ï¼AIæ¥æ(ç¨æ·å·²è¯´è¯)å忢èªå¨å¼¹åº 12 * v15.7 æ´æ°: è¾¾å 飿 ¼ä¼å â æ¶æ¯å ãéç¹ãèªå¨çº¢è²é«äº® + å¨æå¿«æ·æé®ï¼é¶åºç¡/æ³æå/å¨è/卿 ¡/å¾ ä¸ï¼ 13 * v15.6.3 æ´æ°: 忥å端 v5.5ï¼ä¿®æ£å§åååå¸è¯å« 14 * v15.6.2 æ´æ°: åéæé® 3 ç§é(_sendGuard)ï¼è§£å³ Enter + click åæ¶è§¦å导è´çæ¶æ¯åå 15 * v15.6 æ´æ°: å¤å±å端å»é â 5ç§å ¨å±æ¶é´çª + 30ç§æè¿æ¶æ¯å»é + å¿«æ·æé®å·å´ï¼ 16 * é åå端请æ±çº§å»éï¼è§£å³æµå¼å¤±è´¥é级XHR导è´çé夿¶æ¯é®é¢ 17 * v15.5 æ´æ°: Nudgeå级çç¥ â æ²¡ææºå·âæ¨é+å¨åç©¿æ / æææºå·æ²¡è¯¾ç¨âå¼å¯¼è¡¥å è¯¾ç¨ / å ¨æäºâä¸ä¸»å¨æ¨ 18 * v15.4 æ´æ°: Nudgeéé¢ 2åéé´é/åæ¡æ¨é/å·²çèå 19 * v15.3 æ´æ°: 对è¯çç¥å¤§æ¹ï¼å®å¿ä¸¸/æ¶å°¾/䏿¬¡ä¸é®ï¼+ åéæé®å»éå¶ 20 * v15.2 æ´æ°: Nudgeç©¿æç人æé â ä¸å¡+å¨å+ä¸å¡+è¿å¨å+ä¸å¡ äº¤æ¿ 21 * v15.1 æ´æ°: å»æä¾§è¾¹æ Logo + åå¿è³å/å å¿è²äººä¸¤è¡å± ä¸ + åç«¯éæº2-5ç§åå¤å»¶è¿ 22 * v15.0 æ´æ°: åæ å¤´é¨èè²åºåé«åº¦å¯¹é½ + è¾å ¥åºæ©å¤§(æ´ä½åè°) + å³ä¾§åçåºè¡¥é½Logo 23 * v14.0 æ´æ°: çªå£å 宽560px + AI头忾大36px+彩è²è¾¹æ¡ç¯ + æ¸ èå®åæç¤ºé³ 24 * v13.0 æ´æ°: åæ å¸å± â 左侧è天 + å³ä¾§æ¨å¹¿ä¾§è¾¹æ ï¼è¾¨è¯åº¦å¤§å¹ æå 25 * v12.0 æ´æ°: å¼¹çªå¨ç»+å¤´åæ°æ³¡+æç¤ºé³+å¤è½®nudge宿´å¾ªç¯ 26 * v8.0 æ´æ°: è¾¾å 飿 ¼nudge䏻卿¨é + åå¸ç«ç»å¯¹URLå ¼å®¹ + 0.5så¿«éå¼¹åº 27 * 28 * æ ¸å¿å¯¼åï¼å¼å¯¼çèç³»æ¹å¼ -> å»¶è¿æ¨é/è¡¥æ¨ä¼å¾®ç¾¤ 29 * 30 * åè½ï¼ 31 * - å³ä¸è§æ¬æµ®å®¢ææé® + ç¹å»å¼¹åºè天çªå£ 32 * - åæ å¸å±ï¼å·¦è峿¨ï¼ï¼å·¦è¾¹370pxèå¤©åº + å³è¾¹190pxæ¨å¹¿ä¾§è¾¹æ 33 * - å³ä¾§è¾¹æ ï¼è¯¾ç¨æµ·æ¥ãç鍿¹åå¿«æ·å ¥å£ãå è´¹è¯å¬CTA 34 * - èªå¨å¼¹åºï¼0.5ç§ï¼æ¸è¿å¼å ³éé´éï¼ 35 * - å¼¹çªslideUpå¼¹æ§å¨ç»ï¼ä¸åéæåºç° 36 * - DeepSeek AI æºè½å¯¹è¯ + SSE æµå¼éåæ¾ç¤º 37 * - AIæ¶æ¯å带36px大头å + 彩è²è¾¹æ¡ç¯ï¼è¾¨è¯åº¦æé« 38 * - æ°æ¶æ¯æ¸ è"å®å"æç¤ºé³ï¼Web Audio APIï¼ 39 * - çèµè¡¨åï¼å§å+çµè¯+è¯¾ç¨æ¹å+ç¶æï¼ 40 * - å¤è½®ä¸»å¨æ¨éï¼nudgeï¼ï¼5è½®æ¶æ¯å®æ´å¾ªç¯ï¼ä¸éç½®ç´¢å¼ 41 * - é黿¨é触åï¼ç¨æ·ä¸è¯´è¯10ç§åèªå¨è§¦åå端æ¨éæ£æ¥ï¼ 42 * - ä¼ éçé页/å½å页ç»å端ç¨äºç²¾åæ¨é 43 * - åå¸ååå ¼å®¹ï¼æ¯æ window.__SMART_KF_OVERRIDE__ è¦çapiBase/avatarUrl 44 * - ç§»å¨ç«¯ååºå¼ 45 */ 46(function(window, document) { 47 'use strict'; 48 49 // ===== é²éå¤å è½½ä¿æ¤ï¼v21.4ï¼===== 50 // 页é¢ä¸å¯è½åæ¶å卿§å è½½å¨(ydtswt_pc_yd.js)åæ°å è½½å¨(smart-kf-global.js) 51 // å¤å®ä¾ä¼å¯¼è´ session_id åè£ï¼AI æ æ³ä¿æå¯¹è¯ä¸ä¸æ 52 if (window.__SMART_KF_LOADED__) { 53 console.log('[Smart-KF] å·²å è½½ï¼è·³è¿éå¤åå§å'); 54 return; 55 } 56 window.__SMART_KF_LOADED__ = true; 57 58 // ==================== é ç½® ==================== 59 var CONFIG = { 60 siteId: 'qfedu', 61 siteName: 'åéæè²', 62 apiBase: '/test/smart-kf/api', 63 apiToken: 'sk_71bbc60d438c4c64aaa5b278', 64 avatarUrl: '/test/smart-kf/avatar.png', 65 streamEnabled: true, 66 67 autoPopup: { 68 enabled: true, 69 delay: 500, 70 // v21.8 éé¢çç¥ï¼å3次维æåé¢ç(10ç§) â 4-6次60ç§ â 7-9次120ç§ â 10次起300ç§ 71 intervals: [ 72 { maxCount: 3, interval: 10 }, 73 { maxCount: 6, interval: 60 }, 74 { maxCount: 9, interval: 120 }, 75 { maxCount: Infinity, interval: 300 } 76 ] 77 }, 78 79 agent: { 80 name: 'èèå¦å§', 81 avatarImg: true, 82 // v15.7: è¾¾å 飿 ¼é¦æ¡é®å â ä»·å¼âè¡å¨ï¼éç¹ç¨ããæ æ³¨ï¼å端èªå¨é«äº®ï¼ 83 greeting: 'åéãææºå·+å§åãå¯ä»¥ç³è¯·å è´¹è¯å¬åé¢ï¼\nã课ç¨å¤§çº²+å¦ä¹ 路线å¾+就䏿åãä¹å¯ä»¥å é¢ä¸ä»½ççï½' 84 }, 85 86 // çæ¬å· â 卿å è½½ version.jsonï¼åéå¼ä¸ version.json 忥 87 currentVersion: '21.0', 88 versionJsonUrl: '/test/smart-kf/version.json', 89 90 // ===== å³ä¾§æ¨å¹¿ä¾§è¾¹æ ï¼v13 æ°å¢ï¼ ===== 91 sidebar: { 92 enabled: true, 93 // é¡¶é¨åçåº â logo+Sloganï¼é«åº¦ä¸å·¦ä¾§å¤´é¨å¯¹é½ 94 poster: { 95 logoUrl: 'https://www.qfedu.com/wzt/new-logo-white.png', 96 title: 'åå¿è³å å å¿è²äºº' 97 }, 98 // çé¨è¯¾ç¨å¿«æ·å ¥å£ï¼ç¹å»ç´æ¥å起对è¯ï¼ 99 courses: [ 100 { label: 'Javaå¼åï¼å ¨é¾è·¯/JavaEEï¼', trigger: 'ææ³äºè§£Javaå¼å课ç¨' }, 101 { label: 'Python+å¤§æ°æ®åæ', trigger: 'ææ³äºè§£Pythonå¤§æ°æ®åæè¯¾ç¨' }, 102 { label: 'Webå端ï¼HTML5/å ¨æ /鸿èï¼', trigger: 'ææ³äºè§£Webå端课ç¨' }, 103 { label: 'AI人工æºè½ï¼å¤§æ¨¡å/AIGCï¼', trigger: 'ææ³äºè§£AI人工æºè½è¯¾ç¨' }, 104 { label: 'äºè®¡ç®/è¿ç»´ï¼Linux/DevOpsï¼', trigger: 'ææ³äºè§£äºè®¡ç®è¿ç»´è¯¾ç¨' }, 105 { label: 'ç½ç»å®å ¨', trigger: 'ææ³äºè§£ç½ç»å®å ¨è¯¾ç¨' }, 106 { label: 'C++/ç©èç½/åµå ¥å¼', trigger: 'ææ³äºè§£C++ç©èç½åµå ¥å¼è¯¾ç¨' }, 107 { label: 'UI/UE设计ï¼UID/è§æè®¾è®¡ï¼', trigger: 'ææ³äºè§£UI/UE设计课ç¨' }, 108 { label: '软件æµè¯ï¼å«è½¦è½½æµè¯ï¼', trigger: 'ææ³äºè§£è½¯ä»¶æµè¯è¯¾ç¨' }, 109 { label: 'çè§é¢+ç´æçµå', trigger: 'ææ³äºè§£çè§é¢ç´æçµå课ç¨' }, 110 { label: '游æå¼å/VR', trigger: 'ææ³äºè§£æ¸¸æå¼å课ç¨' }, 111 { label: 'æºè½èåºåå ¬', trigger: 'ææ³äºè§£æºè½èåºåå ¬è¯¾ç¨' }, 112 { label: 'æ°è½æºè¿ç»´å·¥ç¨å¸', trigger: 'ææ³äºè§£æ°è½æºè¿ç»´è¯¾ç¨' }, 113 { label: 'æ 人æºé©¾é©¶æ§ç §', trigger: 'ææ³äºè§£æ 人æºé©¾é©¶è¯¾ç¨' }, 114 { label: '认è¯èè¯ï¼PMP/红帽/工信ï¼', trigger: 'ææ³äºè§£è®¤è¯èè¯è¯¾ç¨' } 115 ], 116 // æ¯æ¬¡ç¹å»æå°é´éï¼é²æ¢è¿ç»å¿«éç¹å»æ¶èèµæºï¼ 117 courseClickCooldown: 5000, 118 // åºé¨CTA 119 cta: { 120 text: 'ç¹æçè¨', 121 action: 'leadForm' // 'openChat' æ 'leadForm' 122 } 123 }, 124 125 // ===== ä¿¡æ¯å¼æ v20: ç¨æ·æªè¯´è¯æ¶ï¼æ¯60ç§å¾ªç¯æ¨éé¢è®¾æé ===== 126 // ç¨æ·ä¸æ¦è¯´è¯ â ä¿¡æ¯å¼æåæ¢ï¼AIæ¥ç®¡ 127 infoEngine: { 128 enabled: true, 129 interval: 60000, // æ¯æ¡é´é60ç§ 130 messages: [ 131 'å¨ï½æ³äºè§£ä»ä¹è¯¾ç¨ï¼ç´æ¥é®å°±è¡ï½', 132 'æ¹ä¾¿çä¸ªææºå·åï¼å¦å§å¸®ä½ 宿è¯å¬ï½', 133 'è¿å¨åï¼ç´æ¥ç个çµè¯ï¼é¡¾é®ä¼é对æ¨çæ åµè¯¦ç»ä»ç»ï½', 134 'å«ç¹è±«å¦ï½çä¸ªææºå·å°±è¡ï¼åé¢çæä»¬æ¥æå®ï¼' 135 ] 136 }, 137 138 quickQuestions: [ 139 'ææ³å¨è¯¢è¯¾ç¨', 140 'å¦è´¹å¤å°é±ï¼', 141 'æ ¡åºå¨åªéï¼' 142 ], 143 144 statusButtons: [ 145 { label: 'æå¨è¯»', value: 'å¨è¯»' }, 146 { label: 'æå¨è', value: 'å¨è' }, 147 { label: 'æå¾ ä¸', value: 'å¾ ä¸' } 148 ], 149 150 periodicPushInterval: 300000, // å¯¹è¯æ¡å¼çæ¶æ¯5å鿣æ¥ä¸æ¬¡ï¼æ¯«ç§ï¼ 151 storagePrefix: 'smart_kf_' 152 }; 153 154 // ===== å¤é¨é ç½®è¦çï¼é¡µé¢ä¼ å ¥ window.__SMART_KF_OVERRIDE__ï¼===== 155 if (window.__SMART_KF_OVERRIDE__) { 156 var override = window.__SMART_KF_OVERRIDE__; 157 if (override.apiBase) CONFIG.apiBase = override.apiBase; 158 if (override.apiToken) CONFIG.apiToken = override.apiToken; 159 if (override.avatarUrl) CONFIG.avatarUrl = override.avatarUrl; 160 if (override.siteId) CONFIG.siteId = override.siteId; 161 if (override.siteName) CONFIG.siteName = override.siteName; 162 } 163 164 // ==================== ç¶æ ==================== 165 var STATE = { 166 open: false, 167 minimized: false, 168 messages: [], 169 sessionId: null, 170 closeCount: 0, 171 autoPopupTimer: null, 172 reopenTimer: null, 173 leadSubmitted: false, 174 periodicPushTimer: null, // å¯¹è¯æ¡å¼çæ¶æ¯5åéè¡¥æ¨æ£æ¥ 175 infoEngineTimer: null, // ä¿¡æ¯å¼æå®æ¶å¨ 176 infoEngineIdx: 0, // ä¿¡æ¯å¼æå½åæ¨éæ¶æ¯ç´¢å¼ 177 userHasSpoken: false, // ç¨æ·æ¯å¦å·²è¯´è¯ï¼è¯´è¯åä¿¡æ¯å¼æåæ¢ï¼ 178 lastCourseClick: 0, // è¯¾ç¨æé®é²æ 179 audioCtx: null, 180 userInfo: { 181 name: '', 182 phone: '', 183 course: '', 184 status: '', 185 source: '', 186 landingPage: (function() { try { return document.referrer || 'ç´æ¥è®¿é®'; } catch(e) { return 'ç´æ¥è®¿é®'; } })(), 187 currentPage: (function() { try { return window.location.href; } catch(e) { return ''; } })(), 188 visitTime: new Date().toISOString() 189 } 190 }; 191 192 // ==================== å·¥å ·å½æ° ==================== 193 function el(tag, attrs, children) { 194 var e = document.createElement(tag); 195 if (attrs) { 196 for (var k in attrs) { 197 if (k === 'className') e.className = attrs[k]; 198 else if (k === 'innerHTML') e.innerHTML = attrs[k]; 199 else if (k === 'style' && typeof attrs[k] === 'object') { 200 for (var sk in attrs[k]) e.style[sk] = attrs[k][sk]; 201 } 202 else if (k.indexOf('on') === 0) { 203 e.addEventListener(k.slice(2).toLowerCase(), attrs[k]); 204 } 205 else e.setAttribute(k, attrs[k]); 206 } 207 } 208 if (children) { 209 if (typeof children === 'string') e.textContent = children;
210 else if (Array.isArray(children)) children.forEach(function(c) { if (c) e.appendChild(c); }); 211 else e.appendChild(children); 212 } 213 return e; 214 } 215 216 function makeAvatar(size) { 217 return el('img', { src: CONFIG.avatarUrl, style: { width: size + 'px', height: size + 'px', borderRadius: '50%', objectFit: 'cover' } }); 218 } 219 220 // ===== v14: 带彩è²è¾¹æ¡ç¯ç头åï¼æ¶æ¯ä¸ä½¿ç¨ï¼ ===== 221 function makeAvatarRings(size) { 222 // å¤å±ç¯ + å å±å¤´åï¼è¾¨è¯åº¦æ´é« 223 var wrapper = el('span', { style: { 224 display: 'inline-flex', alignItems: 'flex-end', position: 'relative', 225 width: (size + 6) + 'px', height: (size + 6) + 'px', flexShrink: '0' 226 }}); 227 // å¤ç¯ 228 var ring = el('span', { style: { 229 position: 'absolute', top: '0', left: '0', right: '0', bottom: '0', 230 borderRadius: '50%', background: 'linear-gradient(135deg, #ef5350 0%, #e53935 50%, #e53935 100%)', 231 padding: '3px' 232 }}); 233 // ç½è²å åè®©å¤´åæµ®åº 234 var inner = el('span', { style: { 235 display: 'block', width: '100%', height: '100%', 236 borderRadius: '50%', background: '#fff', 237 overflow: 'hidden' 238 }}); 239 inner.appendChild(el('img', { src: CONFIG.avatarUrl, style: { 240 width: '100%', height: '100%', borderRadius: '50%', objectFit: 'cover' 241 }})); 242 ring.appendChild(inner); 243 wrapper.appendChild(ring); 244 return wrapper; 245 } 246 247 function storageGet(key) { 248 try { 249 return localStorage.getItem(CONFIG.storagePrefix + key) || ''; 250 } catch(e) { return ''; } 251 } 252 function storageSet(key, value) { 253 try { 254 localStorage.setItem(CONFIG.storagePrefix + key, value); 255 } catch(e) {} 256 } 257 258 // ==================== æç¤ºé³ v14: æ¸ è"å®å"ï¼Web Audio APIï¼ ==================== 259 function playNotificationSound() { 260 try { 261 if (!STATE.audioCtx) { 262 STATE.audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 263 } 264 var ctx = STATE.audioCtx; 265 var now = ctx.currentTime; 266 267 // 第ä¸å£°"å®" â æ¸ èçä¿ 268 var o1 = ctx.createOscillator(); 269 var g1 = ctx.createGain(); 270 o1.type = 'sine'; 271 o1.frequency.setValueAtTime(1047, now); // C6 â æ¸ 亮é«é³ 272 o1.frequency.exponentialRampToValueAtTime(880, now + 0.08); 273 g1.gain.setValueAtTime(0.18, now); 274 g1.gain.exponentialRampToValueAtTime(0.001, now + 0.12); 275 o1.connect(g1); 276 g1.connect(ctx.destination); 277 o1.start(now); 278 o1.stop(now + 0.15); 279 280 // 第äºå£°"å" â ç¥ä½ãç¨é¿ãæ´æè´¨æ 281 var o2 = ctx.createOscillator(); 282 var g2 = ctx.createGain(); 283 o2.type = 'sine'; 284 o2.frequency.setValueAtTime(784, now + 0.12); // G5 285 o2.frequency.exponentialRampToValueAtTime(659, now + 0.32); 286 g2.gain.setValueAtTime(0.14, now + 0.12); 287 g2.gain.exponentialRampToValueAtTime(0.001, now + 0.40); 288 o2.connect(g2); 289 g2.connect(ctx.destination); 290 o2.start(now + 0.12); 291 o2.stop(now + 0.42); 292 293 // æ³é³ â å¢å "éé"æ 294 var o3 = ctx.createOscillator(); 295 var g3 = ctx.createGain(); 296 o3.type = 'triangle'; 297 o3.frequency.setValueAtTime(1568, now); // G6 æ³é³ 298 g3.gain.setValueAtTime(0.06, now); 299 g3.gain.exponentialRampToValueAtTime(0.001, now + 0.08); 300 o3.connect(g3); 301 g3.connect(ctx.destination); 302 o3.start(now); 303 o3.stop(now + 0.10); 304 305 } catch(e) {} 306 } 307 308 // ==================== ä¿¡æ¯å¼æ v20 ==================== 309 // ç¨æ·ä»æªè¯´è¯æ¶ï¼æ¯60ç§å¾ªç¯æ¨éé¢è®¾æéæ¶æ¯ 310 // ç¨æ·ä¸æ¦è¯´è¯ â ä¿¡æ¯å¼æç«å»åæ¢ï¼AIæ¥ç®¡ 311 // ==================== ä¿¡æ¯å¼æ v20 ==================== 312 // ç¨æ·ä»æªè¯´è¯æ¶ï¼æ¯60ç§å¾ªç¯æ¨éé¢è®¾æéæ¶æ¯ 313 // ç¨æ·ä¸æ¦è¯´è¯ â ä¿¡æ¯å¼æç«å»åæ¢ï¼AIæ¥ç®¡ 314 315 function startInfoEngine() { 316 if (!CONFIG.infoEngine.enabled) return; 317 if (STATE.userHasSpoken) return; // ç¨æ·å·²è¯´è¯ï¼ä¸å¯å¨ 318 stopInfoEngine(); 319 320 STATE.infoEngineTimer = setTimeout(function() { 321 if (!STATE.open || STATE.userHasSpoken) return; // çªå£å ³äºæç¨æ·å·²è¯´è¯å°±ä¸æ¨ 322 var msgs = CONFIG.infoEngine.messages; 323 var msg = msgs[STATE.infoEngineIdx % msgs.length]; 324 if (msg) { 325 addMessage('agent', msg); 326 playNotificationSound(); 327 } 328 STATE.infoEngineIdx++; 329 startInfoEngine(); // éå½å¯å¨ä¸ä¸æ¡ 330 }, CONFIG.infoEngine.interval); 331 } 332 333 function stopInfoEngine() { 334 if (STATE.infoEngineTimer) { 335 clearTimeout(STATE.infoEngineTimer); 336 STATE.infoEngineTimer = null; 337 } 338 } 339 340 // ==================== DOM æå»º ==================== 341 var $root, $btn, $window, $body, $input, $sendBtn; 342 // v14 尺寸: çªå£560px / å·¦370px / å³190px 343 var WIN_WIDTH = 620; 344 var LEFT_WIDTH = 420; 345 var RIGHT_WIDTH = 200; 346 var AVATAR_SIZE = 36; // æ¶æ¯ä¸AI头å尺寸ï¼v14æ¾å¤§ï¼ 347 348 function injectStyles() { 349 var css = [ 350 // æ ¹å®¹å¨ & æé® 351 '#smart-kf-root { position: fixed; right: 20px; bottom: 20px; z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: 14px; line-height: 1.5; color: #333; }', 352 '#smart-kf-btn { width: 56px; height: 56px; border-radius: 50%; background: #e53935; box-shadow: 0 4px 16px rgba(229,57,53,0.35); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: transform 0.2s, box-shadow 0.2s; position: relative; overflow: hidden; }', 353 '#smart-kf-btn:hover { transform: scale(1.05); box-shadow: 0 6px 20px rgba(229,57,53,0.45); }', 354 '#smart-kf-btn.pulse { animation: skf-pulse 0.6s ease-in-out; }', 355 '@keyframes skf-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.12); } }', 356 '#smart-kf-btn svg { width: 26px;
356 height: 26px; fill: #fff; }', 357 '#smart-kf-btn .skf-close-svg { display: none; }', 358 '#smart-kf-btn.open .skf-chat-svg { display: none; }', 359 '#smart-kf-btn.open .skf-close-svg { display: block; }', 360 /* 红è²èå²å¨ç» - æç»å¸å¼æ³¨æï¼box-shadow å¼å¸ï¼ */ 361 '@keyframes skf-pulse-breath { 0%,100% { box-shadow: 0 4px 16px rgba(229,57,53,0.4), 0 0 0 0 rgba(229,57,53,0.5); } 70% { box-shadow: 0 4px 16px rgba(229,57,53,0.2), 0 0 0 14px rgba(229,57,53,0); } }', 362 '#smart-kf-btn { animation: skf-pulse-breath 2.5s infinite; }', 363 '#smart-kf-btn.open { animation: none; }', 364 '#smart-kf-btn.open { background: #666; }', 365 366 // ============ v14: çªå£å 宽è³560px ============ 367 '#smart-kf-window { position: fixed; right: 20px; bottom: 90px; width: ' + WIN_WIDTH + 'px; max-height: 620px; background: #fff; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); display: none; flex-direction: column; overflow: hidden; opacity: 0; transform: translateY(30px) scale(0.95); }', 368 '#smart-kf-window.open { display: flex; animation: skf-slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }', 369 '@keyframes skf-slideUp { from { opacity: 0; transform: translateY(30px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }', 370 371 // åæ ä¸»è¡ 372 '#smart-kf-main-row { display: flex; flex: 1; min-height: 0; overflow: hidden; }', 373 374 // 左侧èå¤©åº 370pxï¼v14å 宽ï¼â v15: 廿å³è¾¹æ¡ï¼è®©èè²å¤´é¨ä¸å³ä¾§åçåºè¿ææ´ä½ 375 '#smart-kf-left { width: ' + LEFT_WIDTH + 'px; flex-shrink: 0; display: flex; flex-direction: column; overflow: hidden; }', 376 377 // å¤´é¨ 378 '#smart-kf-header { padding: 13px 18px; background: linear-gradient(135deg, #e53935, #b71c1c); color: #fff; display: flex; align-items: center; gap: 12px; flex-shrink: 0; }', 379 '#smart-kf-header .skf-agent-avatar { width: 42px; height: 42px; border-radius: 50%; object-fit: cover; border: 2.5px solid rgba(255,255,255,0.35); }', 380 '#smart-kf-header .skf-agent-name { font-size: 16px; font-weight: 600; }', 381 '#smart-kf-header .skf-agent-status { font-size: 11px; opacity: 0.85; line-height: 1.3; }', 382 '#smart-kf-close { margin-left: auto; cursor: pointer; font-size: 20px; opacity: 0.75; background: none; border: none; color: #fff; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 50%; }', 383 '#smart-kf-close:hover { opacity: 1; background: rgba(255,255,255,0.15); }', 384 385 // èå¤©æ¶æ¯åº â çªå£620px â body 370pxä¿æåè° 386 '#smart-kf-body { flex: 1; overflow-y: auto; padding: 14px 18px; background: #f7f8fa; display: flex; flex-direction: column; gap: 10px; max-height: 370px; }', 387 '.skf-message { max-width: 90%; word-wrap: break-word; animation: skf-fadeIn 0.3s ease; }', 388 '@keyframes skf-fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }', 389 '.skf-msg-agent { align-self: flex-start; display: flex; align-items: center; gap: 8px; }', 390 '.skf-avatar-rings { display: inline-flex; align-items: center; flex-shrink: 0; }', 391 '.skf-msg-user { align-self: flex-end; }', 392 '.skf-bubble { padding: 11px 15px; border-radius: 16px; font-size: 14.5px; line-height: 1.55; }', 393 '.skf-msg-agent .skf-bubble { background: #fff; border: 1px solid #e8eaed; border-bottom-left-radius: 4px; color: #333; }', 394 '.skf-msg-user .skf-bubble { background: #e53935; color: #fff; border-bottom-right-radius: 4px; }', 395 '.skf-msg-typing .skf-bubble { padding: 10px 16px; }', 396 '.skf-typing-dots { display: flex; gap: 4px; }', 397 '.skf-typing-dots span { width: 7px; height: 7px; border-radius: 50%; background: #bbb; animation: skf-typing 1.4s infinite; }', 398 '.skf-typing-dots span:nth-child(2) { animation-delay: 0.2s; }', 399 '.skf-typing-dots span:nth-child(3) { animation-delay: 0.4s; }', 400 '@keyframes skf-typing { 0%,60%,100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-4px); opacity: 1; } }', 401 '.skf-stream-cursor { display: inline; opacity: 1; animation: skf-blink 0.5s infinite; color: #e53935; }', 402 '@keyframes skf-blink { 0%,100% { opacity: 1; } 50% { opacity: 0; } }', 403 404 // å¿«æ·é®é¢ & çèµæé® 405 '#smart-kf-quick { display: flex; gap: 7px; flex-wrap: wrap; padding: 0 18px 8px; flex-shrink: 0; }', 406 '.skf-quick-btn { padding: 8px; background: #fff; border: 1px solid #ef9a9a; border-radius: 18px; font-size: 12.5px; color: #e53935; cursor: pointer; white-space: nowrap; transition: all 0.15s; justify-content: center; display: flex; flex: 1; min-width: 0; text-align: center; }', 407 '.skf-quick-btn:hover { background: #fce4ec; border-color: #e53935; }', 408 // v15.7: æ¶æ¯å é«äº®æ ·å¼ï¼çº¢=ä»·å¼éç¹/è¡å¨ï¼è=ä¿¡æ¯æç¤ºï¼ 409 '.skf-highlight-red { color: #e53935; font-weight: 600; }', 410 '.skf-highlight-blue { color: #1565c0; font-weight: 600; }', 411 // v15.7: æ¶æ¯ä¸æ¹å¿«æ·æé®è¡ï¼è¾¾å 飿 ¼ï¼ç´§è·æ¶æ¯æ°æ³¡ï¼ 412 '.skf-msg-options { display: flex; gap: 8px; padding: 0 0 6px 54px; flex-wrap: wrap; }', 413 '.skf-msg-opt-btn { padding: 5px 14px; background: #fce4ec; border: 1px solid #ef9a9a; border-radius: 14px; font-size: 12.5px; color: #e53935; cursor: pointer; white-space: nowrap; transition: all 0.15s; }', 414 '.skf-msg-opt-btn:hover { background: #f8bbd0; border-color: #e53935; }', 415 '#smart-kf-lead-btn { display: block; margin: 4px 18px 8px; padding: 9px 14px; background: #fff; border: 1.5px dashed #e53935; border-radius: 12px; font-size: 13px; color: #e53935; cursor: pointer; text-align: center; transition: all 0.15s; flex-shrink: 0; }', 416 '#smart-kf-lead-btn:hover { background: #fce4ec; }', 417 '#smart-kf-lead-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255,255,255,0.98); z-index: 10; display: none; flex-direction: column; padding: 24px 20px; overflow-y: auto; }', 418 '#smart-kf-lead-overlay.open { display: flex; }', 419 '.skf-lead-title { font-size: 17px; font-weight: 600; text-align: center; margin-bottom: 20px; color: #e53935; }', 420 '.skf-lead-input { width: 100%; border: 1px solid #ef9a9a; border-radius: 10px; padding: 11px 14px; font-size: 14px; margin-bottom: 12px; outline: none; box-sizing: border-box; transition: border-color 0.2s; }', 421 '.skf-lead-input:focus { border-color: #e53935; }', 422 '.skf-lead-select { width: 100%; border: 1px solid #ef9a9a; border-radius: 10px; padding: 11px 14px; font-size: 14px; margin-bottom: 12px; outline: none; background: #fff; box-sizing: border-box; appearance: auto; }', 423 '.skf-lead-select:focus { border-color: #e53935; }', 424 '.skf-lead-textarea { width: 100%; border: 1px solid #ef9a9a; border-radius: 10px; padding: 11px 14px; font-size: 14px; margin-bottom: 12px; outline: none; resize: vertical; min-height: 70px; font-family: inherit; box-sizing: border-box; transition: border-color 0.2s; }', 425 '.skf-lead-textarea:focus { border-color: #e53935; }', 426 '.skf-lead-submit { width: 100%; padding: 12px; background: #e53935; color: #fff; border: none; border-radius: 10px; font-size: 15px; font-weight: 600; cursor: pointer; margin-top: 6px; transition: background 0.2s; }', 427 '.skf-lead-submit:hover { background: #c62828; }', 428 '.skf-lead-back { width: 100%; padding: 10px; background: none; border: none; color: #999; font-size: 13px; cursor: pointer; margin-top: 8px; }', 429 430 // åºé¨è¾å ¥åº â è¾å ¥æ¡å 大è³56pxï¼ç¨æ·ä½éªï¼å¤ªå°ç¨çé¾åï¼ 431 '#smart-kf-footer { padding: 13px 18px 15px; background: #fff; border-top: 1px solid #eee; display: flex; gap: 10px; align-items: center; flex-shrink: 0; }', 432 '#smart-kf-input { flex: 1; border: 1px solid #ef9a9a; border-radius: 22px; outline: none; font-size: 14px; resize: none; height: 56px; line-height: 24px; font-family: inherit; background: #f7f8fa; transition: border-color 0.2s; padding: 14px 16px; }', 433 '#smart-kf-input:focus { border-color: #e53935; background: #fff; }', 434 '#smart-kf-send { width: 44px; height: 44px; border-radius: 50%; background: #e53935; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; transition: background 0.2s; }', 435 '#smart-kf-send:hover { background: #c62828; }', 436 437 '#smart-kf-send svg { width: 22px; height: 22px; fill: #fff; }', 438 439 // ============ v14: å³ä¾§æ¨å¹¿ä¾§è¾¹æ 190px ============ 440 '#smart-kf-right { width: ' + RIGHT_WIDTH + 'px; flex-shrink: 0; display: flex; flex-direction: column; background: #fff5f5; overflow: hidden; }', 441 442 // é¡¶é¨åçåº â ç«æä¸¤è¡æåå± ä¸ï¼åå¿è³å / å å¿è²äººï¼ï¼æ Logo 443 '.skf-sidebar-poster { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 13px 10px; background: linear-gradient(135deg, #e53935, #b71c1c); color: #fff; flex-shrink: 0; cursor: pointer; transition: opacity 0.2s; min-height: 73px; box-sizing: border-box; }', 444 '.skf-sidebar-poster:hover { opacity: 0.92; }', 445 '.skf-sidebar-poster-text { font-size: 14px; font-weight: 600; letter-spacing: 2px; line-height: 1.6; }', 446 447 // 课ç¨å ¥å£ â 坿»å¨ï¼ç¡®ä¿15é¨è¯¾ç¨å ¨é¨å¯è®¿é® 448 '.skf-sidebar-section { padding: 6px 12px 10px; flex: 1; overflow-y: auto; min-height: 0; }', 449 '.skf-sidebar-label { font-size: 12px; font-weight: 600; color: #e53935; margin-bottom: 6px; padding: 4px 2px 6px; position: sticky; top: 0; background: #fff5f5; z-index: 1; letter-spacing: 0.5px; }', 450 '.skf-sidebar-course { display: block; width: 100%; padding: 8px 10px; margin-bottom: 4px; background: #fff; border: 1px solid #ffcdd2; border-radius: 8px; font-size: 12px; color: #444; cursor: pointer; text-align: left; transition: all 0.15s; line-height: 1.35; }', 451 '.skf-sidebar-course:hover { background: #fce4ec; border-color: #e53935; color: #e53935; }', 452 453 // åºé¨CTA 454 '.skf-sidebar-cta { padding: 12px 12px 12px; margin-top: auto; flex-shrink: 0; }', 455 '.skf-sidebar-cta-btn { display: block; width: 100%; padding: 11px 10px; background: linear-gradient(135deg, #e53935, #c62828); color: #fff; border: none; border-radius: 10px; font-size: 13.5px; font-weight: 600; cursor: pointer; text-align: center; transition: all 0.2s; box-shadow: 0 2px 8px rgba(229,57,53,0.3); }', 456 '.skf-sidebar-cta-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(229,57,53,0.4); }', 457 458 // ååºå¼: ç§»å¨ç«¯éèä¾§è¾¹æ 459 '@media (max-width: 640px) { ' + 460 '#smart-kf-window { right: 0; bottom: 0; width: 100%; max-height: 100%; height: 100%; border-radius: 0; } ' + 461 '#smart-kf-left { width: 100%;
461 border-right: none; } ' + 462 '#smart-kf-right { display: none; } ' + 463 '#smart-kf-body { max-height: none; flex: 1; } ' + 464 '}', 465 466 // v21.0: çæ¬è§æ â è天çªå£å³ä¸è§ï¼å¨æè¯»å window.SMART_KF_VERSION 467 '#skf-version-badge { position: absolute; bottom: 2px; right: 6px; font-size: 10px; color: rgba(0,0,0,0.25); pointer-events: none; z-index: 1; transition: color 0.2s; }', 468 '#smart-kf-window:hover #skf-version-badge { color: rgba(0,0,0,0.5); }' 469 ].join('\n'); 470 471 var style = document.createElement('style'); 472 style.textContent = css; 473 document.head.appendChild(style); 474 } 475 476 function buildSidebar() { 477 var sidebar = CONFIG.sidebar; 478 if (!sidebar.enabled) return null; 479 480 var children = []; 481 482 // === é¡¶é¨åçåºï¼åå¿è³å å å¿è²äºº å± ä¸ï¼æ Logoï¼ === 483 var posterChildren = [ 484 el('span', { className: 'skf-sidebar-poster-text' }, 'åå¿è³å'), 485 el('span', { className: 'skf-sidebar-poster-text' }, 'å å¿è²äºº') 486 ]; 487 488 var $poster = el('div', { 489 className: 'skf-sidebar-poster', 490 onClick: function() { 491 if (STATE.open) showLeadForm(); 492 } 493 }, posterChildren); 494 children.push($poster); 495 496 // === çé¨è¯¾ç¨ === 497 var courseSection = [el('div', { className: 'skf-sidebar-label' }, 'çé¨è¯¾ç¨')]; 498 sidebar.courses.forEach(function(c) { 499 courseSection.push(el('button', { 500 className: 'skf-sidebar-course', 501 onClick: function() { 502 var now = Date.now(); 503 var cooldown = sidebar.courseClickCooldown || 5000; 504 if (STATE.lastCourseClick && now - STATE.lastCourseClick < cooldown) return; 505 STATE.lastCourseClick = now; 506 stopInfoEngine(); 507 sendUserMessage(c.trigger); 508 } 509 }, c.label)); 510 }); 511 512 children.push(el('div', { className: 'skf-sidebar-section' }, courseSection)); 513 514 // === åºé¨CTA === 515 var $cta = el('button', { 516 className: 'skf-sidebar-cta-btn', 517 onClick: function() { 518 if (sidebar.cta.action === 'leadForm' || sidebar.cta.action === 'openChat') { 519 showLeadForm(); 520 } 521 } 522 }, sidebar.cta.text); 523 children.push(el('div', { className: 'skf-sidebar-cta' }, [$cta])); 524 525 return el('div', { id: 'smart-kf-right' }, children); 526 } 527 528 function buildWidget() { 529 $root = el('div', { id: 'smart-kf-root' }); 530 531 $btn = el('div', { id: 'smart-kf-btn', onClick: toggleWindow }, [ 532 el('span', { className: 'skf-chat-svg', innerHTML: '<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.17L4 17.17V4h16v12z"/><path d="M7 9h10v2H7zm0 3h7v2H7z"/></svg>' }), 533 el('span', { className: 'skf-close-svg', innerHTML: '<svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>' }) 534 ]); 535 536 $window = el('div', { id: 'smart-kf-window' }); 537 538 // ===== å¤´é¨ ===== 539 var $header = el('div', { id: 'smart-kf-header' }, [ 540 CONFIG.agent.avatarImg ? el('img', { className: 'skf-agent-avatar', src: CONFIG.avatarUrl, alt: '' }) : el('span', { style: { fontSize: '28px' } }, ''), 541 el('div', {}, [ 542 el('div', { className: 'skf-agent-name' }, CONFIG.agent.name), 543 el('div', { className: 'skf-agent-status' }, 'åéæè²æºè½å®¢ææ£å¨ä¸ºæ¨æå¡') 544 ]), 545 el('button', { id: 'smart-kf-close', onClick: closeWindow }, '\u00d7') 546 ]); 547 548 // ===== èå¤©æ¶æ¯åº ===== 549 $body = el('div', { id: 'smart-kf-body' }); 550 551 // ===== å¿«æ·é®é¢ ===== 552 var $quick = el('div', { id: 'smart-kf-quick' }); 553 CONFIG.quickQuestions.forEach(function(q) { 554 $quick.appendChild(el('button', { className: 'skf-quick-btn', onClick: function() { onQuickClick(q); } }, q)); 555 }); 556 557 // ===== è¾å ¥åº ===== 558 var $footer = el('div', { id: 'smart-kf-footer' }, [ 559 el('textarea', { id: 'smart-kf-input', placeholder: 'è¾å ¥æ¨çé®é¢...', rows: 1, onkeydown: function(e) { 560 if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } 561 }}), 562 el('button', { id: 'smart-kf-send', onClick: sendMessage, innerHTML: '<svg viewBox="0 0 24 24">
562<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>' }) 563 ]); 564 565 // ===== çèµè¦çå± ===== 566 var $leadOverlay = el('div', { id: 'smart-kf-lead-overlay' }); 567 568 // ===== 左侧èå¤©åº ===== 569 var $left = el('div', { id: 'smart-kf-left' }); 570 $left.appendChild($header); 571 $left.appendChild($body); 572 $left.appendChild($quick); 573 $left.appendChild($footer); 574 575 // ===== v14: åæ ä¸»è¡ ===== 576 var $mainRow = el('div', { id: 'smart-kf-main-row' }); 577 $mainRow.appendChild($left); 578 579 // å³ä¾§æ¨å¹¿ä¾§è¾¹æ 580 var $right = buildSidebar(); 581 if ($right) $mainRow.appendChild($right); 582 583 $window.appendChild($mainRow); 584 $window.appendChild($leadOverlay); 585 586 // v21.0: çæ¬è§æ â å¨æè¯»å window.SMART_KF_VERSIONï¼åé CONFIG.currentVersion 587 var _vBadge = el('span', { id: 'skf-version-badge' }); 588 _vBadge.textContent = 'SKF v' + (window.SMART_KF_VERSION || CONFIG.currentVersion); 589 $window.appendChild(_vBadge); 590 591 $root.appendChild($btn); 592 $root.appendChild($window); 593 document.body.appendChild($root); 594 595 $input = document.getElementById('smart-kf-input'); 596 $sendBtn = document.getElementById('smart-kf-send'); 597 } 598 599 // ==================== æ¶æ¯å¤ç ==================== 600 function addMessage(type, text) { 601 STATE.messages.push({ type: type, text: text, time: new Date().toISOString() }); 602 renderMessage(type, text); 603 scrollToBottom(); 604 605 if (type === 'agent') { 606 playNotificationSound(); 607 } 608 } 609 610 function renderMessage(type, text) { 611 var children = []; 612 613 if (type === 'agent') { 614 // v14: 使ç¨å¸¦å½©è²è¾¹æ¡ç¯ç大头å 615 children.push(makeAvatarRings(AVATAR_SIZE)); 616 } 617 618 // v15.7: æ¶æ¯å 容é«äº® â å°ã...ãæ¿æ¢ä¸ºå¸¦æ ·å¼çspan 619 var highlighted = text 620 .replace(/\n/g, '<br>') 621 // ã红è²é«äº®ãâ è¡å¨å·å¬/ä»·å¼éç¹ 622 .replace(/ã(.*?)ã/g, '<span class="skf-highlight-red">$1</span>'); 623 624 children.push(el('div', { className: 'skf-bubble', innerHTML: highlighted })); 625 626 var $msg = el('div', { className: 'skf-message skf-msg-' + type }, children); 627 $body.appendChild($msg); 628 629 // v15.7: å¨æå¿«æ·æé® â AI åå¤ä¸æ¹æ¾ç¤ºå¯éæé®ï¼è¾¾å 飿 ¼ï¼ 630 if (type === 'agent' && text) { 631 var options = detectQuickOptions(text); 632 if (options.length > 0) { 633 var $opts = el('div', { className: 'skf-msg-options' }); 634 options.forEach(function(opt) { 635 $opts.appendChild(el('button', { 636 className: 'skf-msg-opt-btn', 637 onClick: function() { sendUserMessage(opt); } 638 }, opt)); 639 }); 640 $body.appendChild($opts); 641 } 642 } 643 } 644 645 // v15.7: æ ¹æ®AIåå¤å 容ï¼èªå¨å¹é åºæ¾ç¤ºçå¿«æ·é项æé® 646 function detectQuickOptions(text) { 647 var t = text; 648 // ç¶æè¯¢é® â å¨è/卿 ¡/å¾ ä¸ 649 if (/å¨è|卿 ¡|å¾ ä¸|å·¥ä½ç¶æ|ç®åç¶æ/i.test(t) && /\?|ï¼/.test(t)) { 650 return ['å¨è', '卿 ¡', 'å¾ ä¸']; 651 } 652 // åºç¡è¯¢é® â é¶åºç¡/æ³æå 653 if (/é¶åºç¡|0åºç¡|åºç¡|æ¥è§¦|æå|转è¡|å ´è¶£/i.test(t) && /\?|ï¼/.test(t)) { 654 return ['é¶åºç¡', 'æ³æå']; 655 } 656 // è¯¾ç¨æ¹å â å¸¸è§æ¹åå¿«æ·å ¥å£ 657 if (/æ³å¦.*课ç¨|åªä¸ªæ¹å|è¯¾ç¨æ¹å|æå ´è¶£|ä»ä¹ææ¯/i.test(t) && /\?|ï¼/.test(t)) { 658 return ['Java', 'Python', 'å端', 'AI人工æºè½', 'æµè¯']; 659 } 660 // å¦ä¹ ç®ç â 转è¡/æå/å ´è¶£ 661 if (/转è¡|æå|å ´è¶£|ç®ç|æ³.*å¦/i.test(t) && /\?|ï¼/.test(t)) { 662 return ['æ³è½¬è¡', 'æ³æå', 'æå ´è¶£']; 663 } 664 return []; 665 } 666 667 var $typingMsg = null; 668 function addTyping() { 669 if ($typingMsg) return; 670 // v14: æåæç¤ºå¨ä¹å¸¦å¤§å¤´åç¯ 671 var children = [ 672 makeAvatarRings(AVATAR_SIZE), 673 el('div', { className: 'skf-bubble', innerHTML: '<div class="skf-typing-dots"><span></span><span></span><span></span></div>' }) 674 ]; 675 $typingMsg = el('div', { className: 'skf-message skf-msg-agent skf-msg-typing' }, children); 676 $body.appendChild($typingMsg); 677 scrollToBottom(); 678 } 679 680 function removeTyping() { 681 if ($typingMsg) { $typingMsg.remove(); $typingMsg = null; } 682 } 683 684 function scrollToBottom() { 685 setTimeout(function() { $body.scrollTop = $body.scrollHeight; }, 50); 686 } 687 688var _lastSentText = ''; // å 容å»éï¼è®°å½ä¸æ¬¡åéçææ¬ 689var _lastSentTime = 0; // å 容å»éï¼ä¸æ¬¡åéæ¶é´æ³ 690var _quickCooldown = 3000; // å¿«æ·æé®å·å´ï¼3ç§å å䏿¡å¿«æ·é®é¢ä¸å¯éå¤è§¦å
691var _lastQuickClick = 0; // 䏿¬¡å¿«æ·æé®ç¹å»æ¶é´ 692 693function sendMessage() { 694 var text = $input.value.trim(); 695 if (!text) return; 696 $input.value = ''; 697 sendUserMessage(text); 698} 699 700function onQuickClick(text) { 701 var now = Date.now(); 702 if (now - _lastQuickClick < _quickCooldown) { 703 console.log('[Smart-KF] å¿«æ·æé®å·å´ä¸ï¼å¿½ç¥ç¹å»: ' + text); 704 return; 705 } 706 _lastQuickClick = now; 707 sendUserMessage(text); 708} 709 710 // ==================== SSE æµå¼è°ç¨ ==================== 711 var $streamBubble = null; 712 var $streamContainer = null; 713 714 function callAPIStream(message, onFirstToken, onToken, onDone) { 715 if (!STATE.sessionId) { 716 STATE.sessionId = storageGet('session_id') || ''; 717 } 718 719 var _finished = false; // 鲿¢éå¤è°ç¨ finishStream 720 721 fetch(CONFIG.apiBase + '/chat/stream', { 722 method: 'POST', 723 headers: { 724 'Content-Type': 'application/json', 725 'X-API-Token': CONFIG.apiToken 726 }, 727 body: JSON.stringify({ 728 message: message, 729 session_id: STATE.sessionId, 730 source: STATE.userInfo.source || detectSource(), 731 landing_page: STATE.userInfo.landingPage, 732 current_page: STATE.userInfo.currentPage 733 }) 734 }).then(function(response) { 735 if (!response.ok) throw new Error('HTTP ' + response.status); 736 737 var reader = response.body.getReader(); 738 var decoder = new TextDecoder(); 739 var buffer = ''; 740 var fullReply = ''; 741 var firstToken = true; 742 var doneData = null; 743 // v21.7: é¦token 12ç§æ åå¤ -> æç¤ºç½ç»ç¹å¿ï¼é¿å ä¸ç´"æèä¸"ï¼ 744 var _firstTokenTimer = setTimeout(function() { 745 if (firstToken && !_finished) { 746 try { reader.cancel(); } catch(e) {} 747 finishStream(new Error('timeout'), '', null); 748 } 749 }, 12000); 750 751 function processChunk() { 752 reader.read().then(function(result) { 753 if (result.done) { 754 if (!_finished) { 755 _finished = true; 756 finishStream(null, fullReply, doneData); 757 } 758 return; 759 } 760 761 buffer += decoder.decode(result.value, {stream: true}); 762 var lines = buffer.split('\n'); 763 buffer = lines.pop() || ''; 764 765 for (var i = 0; i < lines.length; i++) { 766 var line = lines[i].trim(); 767 if (!line || !line.startsWith('data: ')) continue; 768 769 try { 770 var data = JSON.parse(line.slice(6)); 771 if (data.session_id) { 772 STATE.sessionId = data.session_id; 773 storageSet('session_id', data.session_id); 774 } 775 if (data.token) { 776 if (firstToken) { 777 firstToken = false; 778 clearTimeout(_firstTokenTimer); 779 onFirstToken(); 780 } 781 fullReply += data.token; 782 onToken(data.token, fullReply); 783 } 784 if (data.done) { 785 doneData = data; 786 } 787 if (data.error) { 788 if (!_finished) { 789 _finished = true; 790 finishStream(new Error(data.error), fullReply, null); 791 } 792 return; 793 } 794 } catch(e) {} 795 } 796 797 processChunk(); 798 }).catch(function(err) { 799 if (!_finished) { 800 _finished = true; 801 finishStream(err, fullReply, null); 802 } 803 }); 804 } 805 806 processChunk(); 807 }).catch(function(err) { 808 if (!_finished) { 809 _finished = true; 810 finishStream(err, '', null); 811 } 812 }); 813 814 function finishStream(err, fullReply, doneExtra) { 815 if (_finished && err) return; // å·²ç»éè¿å ¶ä»è·¯å¾finishè¿ï¼ä¸åéå¤ 816 _finished = true; 817 818 if ($streamBubble) { 819 var cursor = $streamBubble.querySelector('.skf-stream-cursor'); 820 if (cursor) cursor.remove(); 821 $streamBubble = null; 822 $streamContainer = null; 823 } 824 825 onDone(err, fullReply, doneExtra); 826 } 827 } 828 829 // v19.2: å建æµå¼è¾åºæ°æ³¡ï¼ä» onFirstToken ä¸ç¬ç«åºæ¥ï¼ä¾¿äºåç«¯å»¶è¿æ§å¶ï¼ 830 function showStreamBubble() { 831 var $msg = el('div', { className: 'skf-message skf-msg-agent' }); 832 // v14: æµå¼æ¶æ¯ä¹å¸¦å¤§å¤´åç¯ 833 $msg.appendChild(makeAvatarRings(AVATAR_SIZE)); 834 $streamContainer = el('div', {}); 835 $streamBubble = el('div', { className: 'skf-bubble' }); 836 $streamBubble.innerHTML = '<span class="skf-stream-cursor">|</span>'; 837 $streamContainer.appendChild($streamBubble); 838 $msg.appendChild($streamContainer); 839 $body.appendChild($msg); 840 scrollToBottom(); 841 } 842 843 function sendUserMessage(text) { 844 // å»éï¼ç¸åææ¬5ç§å ä¸éå¤åéï¼è¦çè¾å ¥æ¡+å¿«æ·æé®+ä¾§è¾¹æ 课ç¨ï¼ 845 var now = Date.now(); 846 if (text === _lastSentText && (now - _lastSentTime) < 5000) { 847 console.log('[Smart-KF] é夿¶æ¯å·²æ¦æª(5ç§æ¶é´çª): ' + text); 848 return; 849 } 850 851 // ç¨æ·è¯´è¯äº â ä¿¡æ¯å¼æåæ¢ï¼AIæ¥ç®¡ 852 STATE.userHasSpoken = true; 853 stopInfoEngine(); 854 855 _lastSentText = text;
856 _lastSentTime = now; 857 858 addMessage('user', text); 859 addTyping(); 860 861 if (CONFIG.streamEnabled && window.fetch && window.ReadableStream) { 862 // åååä¼ è¾ï¼åªèµ°SSEï¼å¤±è´¥ä¸é级XHRï¼æç»ååï¼ 863 callAPIStream(text, 864 function() { 865 // é¦åå°è¾¾ â ç«å³æ¾ç¤ºæµå¼æ°æ³¡ï¼ä¸åäººä¸ºå»¶è¿ 866 removeTyping(); 867 showStreamBubble(); 868 }, 869 function(token, fullText) { 870 if ($streamBubble) { 871 $streamBubble.innerHTML = fullText.replace(/\n/g, '<br>') + '<span class="skf-stream-cursor">|</span>'; 872 scrollToBottom(); 873 } 874 }, 875 function(err, reply, extraData) { 876 if (err) { 877 removeTyping(); 878 if (reply && reply.length > 0) { 879 // æµå¼ä¸æä½å·²æ¶å°é¨ååå¤ â æ¾ç¤ºå·²æå 容 880 addMessage('agent', reply); 881 console.log('[Smart-KF] æµå¼ä¸æï¼å·²æ¾ç¤ºé¨ååå¤ (' + reply.length + 'å)'); 882 } else { 883 // å®å ¨å¤±è´¥ â å好æç¤ºï¼ç¨æ·å¯æå¨éå 884 addMessage('agent', 'ç½ç»æ³¢å¨ï¼è¯·éæ°å鿍çé®é¢ï½'); 885 console.log('[Smart-KF] æµå¼å¤±è´¥:', err.message || err); 886 } 887 return; 888 } 889 890 // SSEæå â æ£å¸¸å¤çåå¤ 891 STATE.messages.push({ type: 'agent', text: reply, time: new Date().toISOString() }); 892 if (extraData) { 893 STATE.sessionId = extraData.session_id; 894 storageSet('session_id', extraData.session_id); 895 if (extraData.user_info) { 896 STATE.userInfo.name = extraData.user_info.name || STATE.userInfo.name; 897 STATE.userInfo.phone = extraData.user_info.phone || STATE.userInfo.phone; 898 STATE.userInfo.course = extraData.user_info.course || STATE.userInfo.course; 899 STATE.userInfo.status = extraData.user_info.status || STATE.userInfo.status; 900 } 901 } 902 playNotificationSound(); 903 } 904 ); 905 } else { 906 // éSSEæµè§å¨ â åªèµ°XHR 907 callAPIXHR(text, function(err, reply) { 908 removeTyping(); 909 var finalReply = (err || !reply) ? getFallbackReply(text) : reply; 910 addMessage('agent', finalReply); 911 }); 912 } 913 } 914 915 // ==================== XHR é级è°ç¨ ==================== 916 function callAPIXHR(message, callback) { 917 if (!STATE.sessionId) { 918 STATE.sessionId = storageGet('session_id') || ''; 919 } 920 921 var xhr = new XMLHttpRequest(); 922 xhr.open('POST', CONFIG.apiBase + '/chat', true); 923 xhr.setRequestHeader('Content-Type', 'application/json'); 924 xhr.setRequestHeader('X-API-Token', CONFIG.apiToken); 925 xhr.timeout = 30000; 926 927 xhr.onload = function() { 928 if (xhr.status === 200) { 929 try { 930 var data = JSON.parse(xhr.responseText); 931 STATE.sessionId = data.session_id; 932 storageSet('session_id', data.session_id); 933 934 if (data.user_info) { 935 STATE.userInfo.name = data.user_info.name || STATE.userInfo.name; 936 STATE.userInfo.phone = data.user_info.phone || STATE.userInfo.phone; 937 STATE.userInfo.course = data.user_info.course || STATE.userInfo.course; 938 STATE.userInfo.status = data.user_info.status || STATE.userInfo.status; 939 } 940 941 callback(null, data.reply); 942 } catch(e) { 943 callback(e, null); 944 } 945 } else { 946 callback(new Error('HTTP ' + xhr.status), null); 947 } 948 }; 949 950 xhr.onerror = function() { callback(new Error('ç½ç»é误'), null); }; 951 xhr.ontimeout = function() { callback(new Error('请æ±è¶ æ¶'), null); }; 952 953 xhr.send(JSON.stringify({ 954 message: message, 955 session_id: STATE.sessionId, 956 source: STATE.userInfo.source || detectSource(), 957 landing_page: STATE.userInfo.landingPage, 958 current_page: STATE.userInfo.currentPage 959 })); 960 } 961 962 // ==================== è¡¥æ¨è§¦å ==================== 963 // çç¥ï¼ 964 // 1. ç¨æ·å ³éå¯¹è¯æ¡æ¶æ£æ¥æ¯å¦ææ°å¯¹è¯ï¼æåè¡¥æ¨ 965 // 2. å¯¹è¯æ¡ä¿æå¼å¯æ¶ï¼æ¯5åéè½®è¯¢ä¸æ¬¡ï¼ææ°å¯¹è¯åè¡¥æ¨ 966 // ä¸¤ç§æ åµé½ç±å端 has_new_messages å¤æï¼æ æ°æ¶æ¯ä¸æ¨ 967 968 function startPeriodicPushTimer() { 969 clearPeriodicPushTimer(); 970 STATE.periodicPushTimer = setInterval(function() { 971 if (STATE.open && STATE.sessionId) { 972 triggerPushCheck(); 973 } 974 }, CONFIG.periodicPushInterval); 975 } 976 977 function clearPeriodicPushTimer() { 978 if (STATE.periodicPushTimer) { 979 clearInterval(STATE.periodicPushTimer); 980 STATE.periodicPushTimer = null; 981 } 982 } 983 984 // triggerPushCheck(onClose) 985 // onClose=true: ç¨æ·å ³éå¯¹è¯æ¡æ¶è§¦åï¼å 许 forceï¼ç»è¿çå¾ æï¼ 986 // onClose=false/undefined: 宿¶æ£æ¥ï¼ä¸¥æ ¼å¤ææ¯å¦ææ°æ¶æ¯ 987 function triggerPushCheck(onClose) { 988 if (!STATE.sessionId) return; 989 990 var xhr = new XMLHttpRequest(); 991 xhr.open('POST', CONFIG.apiBase + '/trigger-push', true); 992 xhr.setRequestHeader('Content-Type', 'application/json'); 993 xhr.setRequestHeader('X-API-Token', CONFIG.apiToken); 994 xhr.timeout = 10000; 995 996 xhr.onload = function() { 997 if (xhr.status === 200) { 998 try { 999 var data = JSON.parse(xhr.responseText); 1000 if (data.pushed) {
1001 console.log('[Smart-KF] è¡¥æ¨å·²è§¦å (onClose=' + (onClose ? 'true' : 'false') + ')'); 1002 } 1003 } catch(e) {} 1004 } 1005 }; 1006 1007 xhr.send(JSON.stringify({ 1008 session_id: STATE.sessionId, 1009 on_close: !!onClose 1010 })); 1011 } 1012 1013 // ==================== é级åå¤ ==================== 1014 function getFallbackReply(text) { 1015 var t = text.toLowerCase(); 1016 1017 if (t.indexOf('课ç¨') !== -1 || t.indexOf('å¹è®') !== -1) { 1018 return 'æä»¬æå端ãJavaãPythonçå¤ä¸ªITæ¹å课ç¨ãæ¨å¯¹åªä¸ªæå ´è¶£ï¼æ¹ä¾¿ç个çµè¯ï¼è®©é¡¾é®ç»æ¨è¯¦ç»ä»ç»~'; 1019 } 1020 if (t.indexOf('å¦è´¹') !== -1 || t.indexOf('ä»·æ ¼') !== -1 || t.indexOf('å¤å°é±') !== -1) { 1021 return 'å¦è´¹ä¸è¬å¨å åå°3ä¸ä¹é´ï¼ç¼ç¨ç±»å¤§æ¦1.5-2.5ä¸ï¼UI设计大æ¦1-2ä¸ï¼å ·ä½çæ¹ååçåãæ¹ä¾¿çä¸ªææºå·åï¼æè®©é¡¾é®ç»æ¨åè¯¦ç»æ¥ä»·å~'; 1022 } 1023 if (t.indexOf('æ ¡åº') !== -1 || t.indexOf('å°å') !== -1 || t.indexOf('å¨åª') !== -1) { 1024 return 'æä»¬å¨å ¨å½å¤ä¸ªåå¸é½ææ ¡åºãæ¨å¨åªä¸ªåå¸ï¼æ¹ä¾¿ç个çµè¯è®©å½å°æ ¡åºé¡¾é®èç³»æ¨~'; 1025 } 1026 if (t.indexOf('å°±ä¸') !== -1 || t.indexOf('å·¥ä½') !== -1) { 1027 return 'æä»¬æä¾ç®åæå¯¼ãé¢è¯è¾ 导ãä¼ä¸å æ¨ç就䏿å¡ãæ¹ä¾¿ç个çµè¯ï¼è®©å°±ä¸èå¸å¸®æ¨åæä¸ä¸~'; 1028 } 1029 if (t.indexOf('å¨è¯»') !== -1 || t.indexOf('å¨è') !== -1 || t.indexOf('å¾ ä¸') !== -1) {
1030 return 'æ¶å°ï¼æ¹ä¾¿ç个çµè¯åï¼è®©è¯¾ç¨é¡¾é®é对æ¨çæ åµå个详ç»çå¦ä¹ è§å~'; 1031 } 1032 if (t.indexOf('ä½ å¥½') !== -1 || t.indexOf('hi') !== -1) { 1033 return 'ä½ å¥½~è¯·é®æ³äºè§£ä»ä¹è¯¾ç¨å¢ï¼æ¹ä¾¿çè¯ç个çµè¯ï¼è®©é¡¾é®ç»æ¨è¯¦ç»ä»ç»~'; 1034 } 1035 return 'æè°¢æ¨çå¨è¯¢ï¼æ¹ä¾¿ç个çµè¯åï¼è®©è¯¾ç¨é¡¾é®ç´æ¥èç³»æ¨è¯¦ç»ä»ç»~'; 1036 } 1037 1038 // ==================== çèµè¡¨å ==================== 1039 function showLeadForm() { 1040 var $overlay = document.getElementById('smart-kf-lead-overlay'); 1041 if (!$overlay || $overlay.classList.contains('open')) return; 1042 1043 $window.style.minHeight = '520px'; 1044 $overlay.innerHTML = ''; 1045 1046 var title = el('div', { className: 'skf-lead-title' }, 'é¢çº¦å è´¹è¯å¬'); 1047 1048 var $nameInput = el('input', { className: 'skf-lead-input', placeholder: 'æ¨çå§å *', value: STATE.userInfo.name }); 1049 var $phoneInput = el('input', { className: 'skf-lead-input', placeholder: 'ææºå·ç *', value: STATE.userInfo.phone, type: 'tel', maxlength: 11 }); 1050 1051 var courses = ['', 'Javaå¼åï¼å ¨é¾è·¯/JavaEEï¼', 'Python+å¤§æ°æ®åæ', 'Webå端ï¼HTML5/å ¨æ /鸿èï¼', 'AI人工æºè½ï¼å¤§æ¨¡å/AIGC/æºå¨è§è§ï¼', 'äºè®¡ç®/è¿ç»´ï¼Linux/DevOpsï¼', 'ç½ç»å®å ¨', 'C++/ç©èç½/åµå ¥å¼', 'UI/UE设计ï¼UID/è§æè®¾è®¡ï¼', '软件æµè¯ï¼å«è½¦è½½æµè¯ï¼', 'çè§é¢+ç´æçµåï¼æ°åªä½è¿è¥ï¼', '游æå¼å/VR', 'æºè½èåºåå ¬ï¼B端产åï¼', 'æ°è½æºè¿ç»´å·¥ç¨å¸', 'æ 人æºé©¾é©¶æ§ç §', '认è¯èè¯ï¼PMP/红帽/工信人æï¼', 'å ¶ä»ITæ¹å']; 1052 var $courseSelect = el('select', { className: 'skf-lead-select' }); 1053 courses.forEach(function(c, i) { 1054 $courseSelect.appendChild(el('option', { value: i === 0 ? '' : c, selected: c === STATE.userInfo.course ? 'selected' : undefined }, c || 'è¯·éæ©å¨è¯¢æ¹å')); 1055 }); 1056 1057 var statuses = ['', 'å¨è¯»', 'å¨è', 'å¾ ä¸']; 1058 var $statusSelect = el('select', { className: 'skf-lead-select' }); 1059 statuses.forEach(function(s) { 1060 $statusSelect.appendChild(el('option', { value: s, selected: s === STATE.userInfo.status ? 'selected' : undefined }, s || 'è¯·éæ©å½åç¶æ')); 1061 }); 1062 1063 // æ°å¢ï¼çè¨æ¡ 1064 var $messageInput = el('textarea', { className: 'skf-lead-textarea', placeholder: 'çè¨ï¼éå¡«ï¼ï¼ä¾å¦ï¼æ³äºè§£å¼çæ¶é´ãå¦è´¹ãä¸è¯¾æ¹å¼ç...', rows: 2 }); 1065 1066 var $submit = el('button', { className: 'skf-lead-submit' }, 'æäº¤é¢çº¦'); 1067 1068 $submit.addEventListener('click', function() { 1069 if ($submit.disabled) return; 1070 submitLeadForm($nameInput, $phoneInput, $courseSelect, $statusSelect, $messageInput, $submit); 1071 }); 1072 1073 var $back = el('button', { className: 'skf-lead-back', onClick: hideLeadForm }, 'è¿å对è¯'); 1074 1075 $overlay.appendChild(title); 1076 $overlay.appendChild($nameInput); 1077 $overlay.appendChild($phoneInput); 1078 $overlay.appendChild($courseSelect); 1079 $overlay.appendChild($statusSelect); 1080 $overlay.appendChild($messageInput); 1081 $overlay.appendChild($submit); 1082 $overlay.appendChild($back); 1083 1084 $overlay.classList.add('open'); 1085 } 1086 1087 function hideLeadForm() {
1088 var $overlay = document.getElementById('smart-kf-lead-overlay'); 1089 if ($overlay) $overlay.classList.remove('open'); 1090 $window.style.minHeight = ''; 1091 } 1092 1093 function submitLeadForm($nameInput, $phoneInput, $courseSelect, $statusSelect, $messageInput, $submitBtn) { 1094 var name = $nameInput.value.trim(); 1095 var phone = $phoneInput.value.trim(); 1096 var course = $courseSelect.value; 1097 var status = $statusSelect.value; 1098 var message = $messageInput ? $messageInput.value.trim() : ''; 1099 1100 if (!name) { alert('请è¾å ¥æ¨çå§å'); return; } 1101 if (!phone) { alert('请è¾å ¥ææºå·ç '); return; } 1102 if (!/^1[3-9]\d{9}$/.test(phone)) { alert('请è¾å ¥æ£ç¡®çææºå·ç '); return; } 1103 1104 STATE.userInfo.name = name; 1105 STATE.userInfo.phone = phone; 1106 if (course) STATE.userInfo.course = course; 1107 if (status) STATE.userInfo.status = status; 1108 STATE.userInfo.source = detectSource(); 1109 STATE.userInfo.landingPage = document.referrer || 'ç´æ¥è®¿é®'; 1110 STATE.userInfo.currentPage = window.location.href; 1111 1112 $submitBtn.disabled = true; 1113 $submitBtn.textContent = 'æäº¤ä¸...'; 1114 1115 var xhr = new XMLHttpRequest(); 1116 xhr.open('POST', CONFIG.apiBase + '/submit-lead', true); 1117 xhr.setRequestHeader('Content-Type', 'application/json'); 1118 xhr.setRequestHeader('X-API-Token', CONFIG.apiToken); 1119 xhr.timeout = 15000; 1120 1121 xhr.onload = function() { 1122 $submitBtn.disabled = false; 1123 $submitBtn.textContent = 'æäº¤'; 1124 1125 if (xhr.status === 200) { 1126 try { 1127 var data = JSON.parse(xhr.responseText); 1128 hideLeadForm(); 1129 1130 if (data.success) { 1131 STATE.leadSubmitted = true; 1132 1133 var confirmMsg = 'å·²æ¶å°æ¨çé¢çº¦ï¼<br><br>'; 1134 if (name) confirmMsg += 'å§åï¼<b>' + name + '</b><br>'; 1135 if (phone) confirmMsg += 'çµè¯ï¼<b>' + phone + '</b><br>'; 1136 if (course) confirmMsg += 'å¨è¯¢è¯¾ç¨ï¼<b>' + course + '</b><br>'; 1137 if (status) confirmMsg += 'å½åç¶æï¼<b>' + status + '</b><br>'; 1138 if (message) confirmMsg += 'çè¨ï¼<b>' + message + '</b><br>'; 1139 confirmMsg += '<br>课ç¨é¡¾é®ä¼å°½å¿«ä¸æ¨èç³»ï¼è¯·ä¿æææºç éã'; 1140 1141 addMessage('agent', confirmMsg); 1142 1143 var missing = []; 1144 if (!course) missing.push('æ³å¨è¯¢çè¯¾ç¨æ¹å'); 1145 if (!status) missing.push('å½åçç¶æï¼å¨è¯»/å¨è/å¾ ä¸ï¼'); 1146 if (missing.length > 0) { 1147 setTimeout(function() { 1148 addMessage('agent', '对äºï¼æ¨æ¹ä¾¿è¯´ä¸ä¸<b>' + missing.join('ã') + '</b>åï¼è¿æ ·é¡¾é®èç³»æ¨çæ¶åå¯ä»¥æ´æé对æ§~'); 1149 }, 1500); 1150 } 1151 } else { 1152 alert('æäº¤å¤±è´¥ï¼' + (data.message || '请ç¨åéè¯')); 1153 } 1154 } catch(e) { 1155 alert('æäº¤å¤±è´¥ï¼è¯·ç¨åéè¯'); 1156 } 1157 } else { 1158 alert('æäº¤å¤±è´¥ï¼' + xhr.status + 'ï¼ï¼è¯·ç¨åéè¯'); 1159 } 1160 }; 1161 1162 xhr.onerror = function() { 1163 $submitBtn.disabled = false; 1164 $submitBtn.textContent = 'æäº¤'; 1165 alert('ç½ç»é误ï¼è¯·ç¨åéè¯'); 1166 }; 1167 1168 xhr.send(JSON.stringify({ 1169 name: name, 1170 phone: phone, 1171 course: course, 1172 status: status, 1173 message: message, 1174 source: STATE.userInfo.source, 1175 landing_page: STATE.userInfo.landingPage, 1176 current_page: STATE.userInfo.currentPage, 1177 session_id: STATE.sessionId 1178 })); 1179 } 1180 1181 // ==================== æ¥æºè¯å« ==================== 1182 function extractSearchKeyword(ref) { 1183 try { 1184 var url = new URL(ref); 1185 var host = url.hostname; 1186 var kw = ''; 1187 if (host.indexOf('baidu.com') !== -1) { 1188 kw = url.searchParams.get('wd') || url.searchParams.get('word') || ''; 1189 } else if (host.indexOf('google.com') !== -1 || host.indexOf('google.co') !== -1) { 1190 kw = url.searchParams.get('q') || ''; 1191 } else if (host.indexOf('sogou.com') !== -1) { 1192 kw = url.searchParams.get('query') || ''; 1193 } else if (host.indexOf('bing.com') !== -1) { 1194 kw = url.searchParams.get('q') || ''; 1195 } else if (host.indexOf('360.cn') !== -1 || host.indexOf('so.com') !== -1) { 1196 kw = url.searchParams.get('q') || ''; 1197 } else if (host.indexOf('sm.cn') !== -1) { 1198 kw = url.searchParams.get('q') || ''; 1199 } else if (host.indexOf('zhihu.com') !== -1) { 1200 kw = url.searchParams.get('q') || url.searchParams.get('search') || ''; 1201 } 1202 if (kw) { 1203 try { return decodeURIComponent(kw); } catch(e2) { return kw; } 1204 } 1205 } catch(e) {} 1206 return ''; 1207 } 1208 1209 function detectSource() { 1210 var ref = document.referrer || ''; 1211 var params = new URLSearchParams(window.location.search); 1212 1213 if (ref.indexOf('baidu.com') !== -1) { 1214 var kw = extractSearchKeyword(ref); 1215 return kw ? 'ç¾åº¦æç´¢(' + kw + ')' : 'ç¾åº¦æç´¢'; 1216 } 1217 if (ref.indexOf('google.com') !== -1) { 1218 var gkw = extractSearchKeyword(ref); 1219 return gkw ? 'Googleæç´¢(' + gkw + ')' : 'Googleæç´¢'; 1220 } 1221 if (ref.indexOf('sogou.com') !== -1) { 1222 var skw = extractSearchKeyword(ref); 1223 return skw ? 'æçæç´¢(' + skw + ')' : 'æçæç´¢'; 1224 } 1225 if (ref.indexOf('360.cn') !== -1 || ref.indexOf('so.com') !== -1) { 1226 var kw360 = extractSearchKeyword(ref); 1227 return kw360 ? '360æç´¢(' + kw360 + ')' : '360æç´¢'; 1228 } 1229 if (ref.indexOf('bing.com') !== -1) { 1230 var kwbing = extractSearchKeyword(ref); 1231 return kwbing ? 'Bingæç´¢(' + kwbing + ')' : 'Bingæç´¢'; 1232 } 1233 if (ref.indexOf('sm.cn') !== -1) { 1234 var kwsm = extractSearchKeyword(ref); 1235 return kwsm ? 'ç¥é©¬æç´¢(' + kwsm + ')' : 'ç¥é©¬æç´¢'; 1236 } 1237 if (ref.indexOf('zhihu.com') !== -1) { 1238 var kwzh = extractSearchKeyword(ref); 1239 return kwzh ? 'ç¥ä¹æç´¢(' + kwzh + ')' : 'ç¥ä¹æç´¢'; 1240 } 1241 if (params.get('utm_source')) return params.get('utm_source'); 1242 if (params.get('source')) return params.get('source'); 1243 if (params.get('bd_vid')) return 'ç¾åº¦æ¨å¹¿'; 1244 if (ref) {
1245 try { return 'å¤é¨é¾æ¥(' + (new URL(ref)).hostname.replace('www.', '') + ')'; } 1246 catch(e) { return 'å¤é¨é¾æ¥'; } 1247 } 1248 return 'ç´æ¥è®¿é®'; 1249 } 1250 1251 // ==================== çªå£æ§å¶ ==================== 1252 function openWindow() { 1253 STATE.open = true; 1254 $window.classList.add('open'); 1255 $btn.classList.add('open'); 1256 1257 if (STATE.messages.length === 0) { 1258 addMessage('agent', CONFIG.agent.greeting); 1259 } 1260 scrollToBottom(); 1261 1262 // ç¨æ·æªè¯´è¯æ¶å¯å¨ä¿¡æ¯å¼æ 1263 if (!STATE.userHasSpoken) { 1264 startInfoEngine(); 1265 } 1266 startPeriodicPushTimer(); // å¯¹è¯æ¡å¼çæ¶æ¯5å鿣æ¥ä¸æ¬¡ 1267 } 1268 1269 function closeWindow() { 1270 STATE.open = false; 1271 $window.classList.remove('open'); 1272 $btn.classList.remove('open'); 1273 clearPeriodicPushTimer(); 1274 stopInfoEngine(); 1275 1276 STATE.closeCount++; 1277 storageSet('close_count', STATE.closeCount); 1278 1279 // å ³éæ¶è§¦å䏿¬¡è¡¥æ¨æ£æ¥ï¼ä» å½ææ°æ¶æ¯æ¶å端æä¼çæ£æ¨éï¼ 1280 if (STATE.sessionId) { 1281 triggerPushCheck(true); // onClose=trueï¼å 许 force ç»è¿çå¾ æ 1282 } 1283 1284 scheduleReopen(); 1285 } 1286 1287 function toggleWindow() { 1288 if (STATE.open) closeWindow(); 1289 else openWindow(); 1290 } 1291 1292 function scheduleReopen() { 1293 if (!CONFIG.autoPopup.enabled) return; 1294 if (STATE.userHasSpoken) return; // v21.8: AI å·²æ¥æï¼ç¨æ·å·²è¯´è¯ï¼ï¼åæ¢èªå¨å¼¹åº 1295 if (STATE.reopenTimer) clearTimeout(STATE.reopenTimer); 1296 1297 var delay = 300; 1298 for (var i = 0; i < CONFIG.autoPopup.intervals.length; i++) { 1299 if (STATE.closeCount <= CONFIG.autoPopup.intervals[i].maxCount) { 1300 delay = CONFIG.autoPopup.intervals[i].interval * 1000; 1301 break; 1302 } 1303 } 1304 1305 STATE.reopenTimer = setTimeout(function() { 1306 if (!STATE.open) openWindow(); 1307 }, delay); 1308 } 1309 1310 function loadCloseCount() { 1311 var count = storageGet('close_count'); 1312 if (count) STATE.closeCount = parseInt(count) || 0; 1313 } 1314 1315 // ==================== èªå¨å¼¹åº ==================== 1316 function initAutoPopup() { 1317 if (!CONFIG.autoPopup.enabled) return; 1318 loadCloseCount(); 1319 1320 STATE.autoPopupTimer = setTimeout(function() { 1321 if (!STATE.open) { 1322 openWindow(); 1323 $btn.classList.add('pulse'); 1324 setTimeout(function() { $btn.classList.remove('pulse'); }, 600); 1325 } 1326 }, CONFIG.autoPopup.delay); 1327 } 1328 1329 // ==================== å¯¹å¤ API ==================== 1330 window.SmartKF = { 1331 open: openWindow, 1332 close: closeWindow, 1333 toggle: toggleWindow, 1334 showLead: showLeadForm, 1335 sendMessage: sendUserMessage, 1336 getMessages: function() { return STATE.messages; }, 1337 getUserInfo: function() { return STATE.userInfo; }, 1338 getState: function() { return STATE; }, 1339 config: CONFIG 1340 }; 1341 1342 // ==================== ä¼è¯æ¢å¤ï¼è·¨é¡µé¢/å·æ°ï¼ ==================== 1343 function restoreSession() { 1344 var savedSessionId = storageGet('session_id'); 1345 if (!savedSessionId) return; 1346 1347 STATE.sessionId = savedSessionId; 1348 1349 // ååç«¯è¯·æ±æ¢å¤å岿¶æ¯
1350 fetch(CONFIG.apiBase + '/session', { 1351 method: 'POST', 1352 headers: { 1353 'Content-Type': 'application/json', 1354 'X-API-Token': CONFIG.apiToken 1355 }, 1356 body: JSON.stringify({ session_id: savedSessionId }) 1357 }).then(function(r) { return r.json(); }) 1358 .then(function(data) { 1359 if (!data.history || data.history.length === 0) return; 1360 1361 // é²å¾¡ï¼å¦æç¨æ·å·²ç»å¨æ¢å¤æé´åäºæ°æ¶æ¯ï¼ä¸åè¦ç 1362 if (STATE.messages.length > 0) return; 1363 1364 var history = data.history; 1365 var hasUserMessage = false; 1366 for (var i = 0; i < history.length; i++) { 1367 var h = history[i]; 1368 var type = (h.role === 'assistant') ? 'agent' : 'user'; 1369 var text = h.content || ''; 1370 STATE.messages.push({ type: type, text: text, time: new Date().toISOString() }); 1371 renderMessage(type, text); 1372 if (type === 'user') hasUserMessage = true; 1373 } 1374 scrollToBottom(); 1375 1376 // 妿åå²ä¸æç¨æ·æ¶æ¯ï¼æ è®°ä¸ºç¨æ·å·²è¯´è¯ï¼é¿å ä¿¡æ¯å¼æéæ°å¯å¨ 1377 if (hasUserMessage) { 1378 STATE.userHasSpoken = true; 1379 stopInfoEngine(); 1380 } 1381 1382 // æ¢å¤å·²æ¶éçç¨æ·ä¿¡æ¯ 1383 if (data.user_info) { 1384 if (data.user_info.name) STATE.userInfo.name = data.user_info.name; 1385 if (data.user_info.phone) STATE.userInfo.phone = data.user_info.phone; 1386 if (data.user_info.course) STATE.userInfo.course = data.user_info.course; 1387 if (data.user_info.status) STATE.userInfo.status = data.user_info.status; 1388 } 1389 1390 console.log('Smart-KF: æ¢å¤ ' + history.length + ' æ¡å岿¶æ¯ | session=' + savedSessionId); 1391 }) 1392 .catch(function(e) { 1393 console.warn('Smart-KF: ä¼è¯æ¢å¤å¤±è´¥ï¼ä½¿ç¨å ¨æ°ä¼è¯', e.message); 1394 STATE.sessionId = null; 1395 storageSet('session_id', ''); 1396 }); 1397 } 1398 1399 // ==================== å è½½çæ¬å· ==================== 1400 function _loadVersion() { 1401 // å·²æå¼åè·³è¿ï¼é²æ¢éå¤è¯·æ±ï¼ 1402 if (window.SMART_KF_VERSION) return; 1403 1404 // ç¡®å® version.json URLï¼ä¼å ç¨ CONFIG.versionJsonUrlï¼å¦åæ ¹æ® widget èæ¬è·¯å¾æ¨ç® 1405 var vUrl = CONFIG.versionJsonUrl; 1406 if (!vUrl) { 1407 // æ ¹æ® widget èæ¬ç src æ¨ç®ï¼widget_v16.js æå¨ç®å½ä¸ç version.json 1408 var scriptEl = document.getElementById('smart-kf-widget-script'); 1409 if (scriptEl && scriptEl.src) { 1410 vUrl = scriptEl.src.replace(/widget_v16\.js(\?.*)?$/, 'version.json'); 1411 } 1412 } 1413 1414 if (!vUrl) return; // æ æ³ç¡®å®è·¯å¾ï¼è·³è¿ 1415 1416 // æ·»å ç¼åç ´ååæ°ï¼ç¡®ä¿æ¯æ¬¡æ¿å°ææ°çæ¬ 1417 var cacheBust = vUrl.indexOf('?') > -1 ? '&_t=' : '?_t='; 1418 cacheBust += Date.now(); 1419 1420 try { 1421 var xhr = new XMLHttpRequest(); 1422 xhr.open('GET', vUrl + cacheBust, true); 1423 xhr.timeout = 5000; 1424 xhr.onreadystatechange = function() { 1425 if (xhr.readyState === 4 && xhr.status === 200) { 1426 try { 1427 var data = JSON.parse(xhr.responseText); 1428 if (data && data.version) { 1429 window.SMART_KF_VERSION = data.version; 1430 console.log('[Smart-KF] çæ¬å·å·²å è½½: v' + data.version); 1431 // ç«å³å·æ°è§æ 1432 var badge = document.getElementById('skf-version-badge'); 1433 if (badge) { 1434 badge.textContent = 'SKF v' + data.version; 1435 } 1436 } 1437 } catch(e) { 1438 console.warn('[Smart-KF] version.json è§£æå¤±è´¥:', e.message); 1439 window.SMART_KF_VERSION = CONFIG.currentVersion; 1440 } 1441 } else if (xhr.readyState === 4) { 1442 // å 载失败ï¼ä½¿ç¨åéå¼ 1443 window.SMART_KF_VERSION = CONFIG.currentVersion; 1444 console.warn('[Smart-KF] version.json å 载失败(status=' + xhr.status + '), 使ç¨åéçæ¬ v' + CONFIG.currentVersion); 1445 } 1446 }; 1447 xhr.send(); 1448 } catch(e) { 1449 window.SMART_KF_VERSION = CONFIG.currentVersion; 1450 } 1451 } 1452 1453 // ==================== åå§å ====================
1454 function init() { 1455 injectStyles(); 1456 buildWidget(); 1457 1458 if (!STATE.userInfo.source) { 1459 STATE.userInfo.source = detectSource(); 1460 } 1461 if (!STATE.userInfo.landingPage || STATE.userInfo.landingPage === 'ç´æ¥è®¿é®') { 1462 STATE.userInfo.landingPage = document.referrer || 'ç´æ¥è®¿é®'; 1463 } 1464 STATE.userInfo.currentPage = window.location.href; 1465 1466 // å°è¯æ¢å¤è·¨é¡µé¢/å·æ°çèå¤©è®°å½ 1467 restoreSession(); 1468 1469 // å è½½ version.json 并设置 window.SMART_KF_VERSION 1470 _loadVersion(); 1471 1472 // çæ¬è§æ â çå¬ window.SMART_KF_VERSION ååï¼å¨æå·æ° 1473 var _vBadge = document.getElementById('skf-version-badge'); 1474 if (_vBadge) { 1475 // ç«å³å·æ°ä¸æ¬¡ï¼åéå¼ä¸º CONFIG.currentVersionï¼ 1476 _vBadge.textContent = 'SKF v' + (window.SMART_KF_VERSION || CONFIG.currentVersion); 1477 // æ¯2ç§æ£æ¥çæ¬ååï¼version.json 弿¥å è½½åèªå¨å·æ°è§æ ï¼ 1478 var _vCheckTimer = setInterval(function() { 1479 var v = window.SMART_KF_VERSION || CONFIG.currentVersion; 1480 if (_vBadge.textContent !== 'SKF v' + v) { 1481 _vBadge.textContent = 'SKF v' + v; 1482 console.log('[Smart-KF] çæ¬è§æ å·²æ´æ°: v' + v); 1483 } 1484 }, 2000); 1485 // 10ç§ååæ¢æ£æ¥ï¼version.json åºå·²å è½½å®æ¯ï¼ 1486 setTimeout(function() { clearInterval(_vCheckTimer); }, 10000); 1487 } 1488 1489 initAutoPopup(); 1490 1491 console.log('Smart-KF v20.0 已就绪 | åå¼ææ¶æ: ä¿¡æ¯å¼æ(60ç§è½®æ¨) + AIèªç±å¯¹è¯ | çæ¬è§æ å¨æè¯»å'); 1492 console.log('æµå¼: ' + (CONFIG.streamEnabled && window.fetch && window.ReadableStream ? 'å¯ç¨' : 'é级XHR') + ' | èªå¨å¼¹åº: ' + CONFIG.autoPopup.delay/1000 + 'ç§ | ä¿¡æ¯å¼æ: ' + (CONFIG.infoEngine.enabled ? 'å¯ç¨' : 'å ³é')); 1493 } 1494 1495 if (document.readyState === 'loading') { 1496 document.addEventListener('DOMContentLoaded', init); 1497 } else { 1498 init(); 1499 } 1500 1501})(window, document);
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.