1/* 2 * Copyright (C) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the 'License'); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF unknown KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseElement, element } from '../../base-ui/BaseElement'; 17import { AiResponse, SpStatisticsHttpUtil } from '../../statistics/util/SpStatisticsHttpUtil'; 18import { threadPool } from '../database/SqlLite'; 19import { SpAiAnalysisPageHtml } from './SpAiAnalysisPage.html'; 20import { getTimeString } from './trace/sheet/TabPaneCurrentSelection'; 21import { WebSocketManager } from '../../webSocket/WebSocketManager'; 22import { TypeConstants } from '../../webSocket/Constants'; 23import { TraceRow } from './trace/base/TraceRow'; 24import { SpSystemTrace } from './SpSystemTrace'; 25import { SpApplication } from '../SpApplication'; 26import { Utils } from './trace/base/Utils'; 27import { LitTable } from '../../base-ui/table/lit-table'; 28 29const TITLE_HEIGHT = 41; 30const TBODY_HEIGHT = 160; 31 32@element('sp-ai-analysis') 33export class SpAiAnalysisPage extends BaseElement { 34 valueChangeHandler: ((str: string, id: number) => void) | undefined | null; 35 private askQuestion: Element | null | undefined; 36 private q_a_window: HTMLDivElement | null | undefined; 37 private aiAnswerBox: HTMLDivElement | null | undefined; 38 private newChatEl: HTMLImageElement | null | undefined; 39 private contentWindow: HTMLDivElement | null | undefined; 40 private inputEl: HTMLTextAreaElement | null | undefined; 41 private tipsContainer: HTMLDivElement | null | undefined; 42 private chatImg: HTMLImageElement | null | undefined; 43 private reportBar: HTMLImageElement | null | undefined; 44 private reportImg: HTMLImageElement | null | undefined; 45 private sendImg: HTMLImageElement | null | undefined; 46 private draftBtn: HTMLDivElement | null | undefined; 47 private downloadBtn: HTMLDivElement | null | undefined; 48 private draftList: HTMLDivElement | null | undefined; 49 private tipsContent: HTMLDivElement | null | undefined; 50 private loadingItem: HTMLDivElement | null | undefined; 51 private startTimeEl: HTMLSpanElement | null | undefined; 52 private endTimeEl: HTMLSpanElement | null | undefined; 53 private contentsTable: LitTable | null | undefined; 54 private chatBar: HTMLDivElement | null | undefined; 55 private reportDetails: HTMLDivElement | null | undefined; 56 private showPageFlag: string = 'chat'; 57 private tipContentArr: Array<string> = []; 58 private question: string = ''; 59 private chatToken: string = ''; 60 private detectToken: string = ''; 61 // 是否点击了新建聊天 62 private isNewChat: boolean = false; 63 isCtrlDown: boolean = false; 64 static isRepeatedly: boolean = false; 65 // 拼接下载内容 66 private reportContent: string = ''; 67 private isNodata: boolean = true; 68 private md: unknown; 69 private isResultBack: boolean = true; 70 private timerId: unknown = undefined; 71 private getSugBtnList: Array<unknown> = []; 72 static startTime: number = 0; 73 static endTime: number = 0; 74 activeTime: Element | undefined | null; 75 // 监听选中时间范围变化 76 static selectChangeListener(startTime: number, endTime: number): void { 77 SpAiAnalysisPage.startTime = startTime; 78 SpAiAnalysisPage.endTime = endTime; 79 if ( 80 document.querySelector('body > sp-application') && 81 document.querySelector('body > sp-application')!.shadowRoot && 82 document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis') && 83 document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis')!.shadowRoot 84 ) { 85 let startEl = document 86 .querySelector('body > sp-application')! 87 .shadowRoot!.querySelector('#sp-ai-analysis')! 88 .shadowRoot!.querySelector('div.chatBox > div > div.report_details > div.selectionBox > div.startBox > span'); 89 startEl && (startEl.innerHTML = getTimeString(startTime).toString()); 90 let endEl = document 91 .querySelector('body > sp-application')! 92 .shadowRoot!.querySelector('#sp-ai-analysis')! 93 .shadowRoot!.querySelector('div.chatBox > div > div.report_details > div.selectionBox > div.endBox > span'); 94 endEl && (endEl.innerHTML = getTimeString(endTime).toString()); 95 } 96 } 97 initElements(): void { 98 this.md = require('markdown-it')({ 99 html: true, 100 typographer: true 101 }); 102 // 自定义 link_open 规则 103 // @ts-ignore 104 this.md.renderer.rules.link_open = (tokens, idx): string => { 105 // @ts-ignore 106 const href = tokens![idx].attrIndex('href'); 107 if (href < 0) { 108 return ''; 109 } 110 // @ts-ignore 111 tokens[idx].attrPush(['target', '_blank']); // 添加 target="_blank" 112 // @ts-ignore 113 tokens[idx].attrPush(['rel', 'noopener noreferrer']); // 推荐添加 rel="noopener noreferrer" 以提高安全性 114 // @ts-ignore 115 return `<a href="${tokens[idx].attrs[href][1]}" target="_blank" rel="noopener noreferrer">`; 116 }; 117 let aiAssistant = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis'); 118 this.chatBar = this.shadowRoot?.querySelector('.chatBar'); 119 let closeBtn = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis')!.shadowRoot!.querySelector('div.rightTabBar > lit-icon')!.shadowRoot!.querySelector('#icon'); 120 this.askQuestion = this.shadowRoot?.querySelector('.ask_question'); 121 this.reportBar = this.shadowRoot?.querySelector('.report'); 122 this.q_a_window = this.shadowRoot?.querySelector('.q_a_window'); 123 this.reportDetails = this.shadowRoot?.querySelector('.report_details'); 124 this.contentWindow = this.shadowRoot?.querySelector('.ask_question'); 125 this.tipsContainer = this.shadowRoot?.querySelector('.tipsContainer'); 126 this.inputEl = this.shadowRoot?.querySelector('.inputText'); 127 this.chatImg = this.shadowRoot?.querySelector('.chatBar')?.getElementsByTagName('img')[0]; 128 this.reportImg = this.shadowRoot?.querySelector('.report')?.getElementsByTagName('img')[0]; 129 this.sendImg = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis')!.shadowRoot?.querySelector('div.chatInputBox > div.chatInput > img'); 130 this.newChatEl = document.querySelector('body > sp-application')!.shadowRoot!.querySelector('#sp-ai-analysis')!.shadowRoot?.querySelector('div.chatBox > div > div.ask_question > div.chatInputBox > div.chatConfig > div > div.newChat > img'); 131 // 诊断按钮 132 this.draftBtn = this.shadowRoot?.querySelector('.analysisBtn'); 133 // 下载报告按钮 134 this.downloadBtn = this.shadowRoot?.querySelector('.downloadBtn'); 135 // 报告列表 136 this.draftList = this.shadowRoot?.querySelector('.data-record'); 137 // 空数据页面 138 this.tipsContent = this.shadowRoot?.querySelector('.tips-content'); 139 // 时间展示区域 140 this.startTimeEl = this.shadowRoot?.querySelector('.startTime'); 141 // 诊断信息汇总列表 142 this.contentsTable = this.shadowRoot?.querySelector('#tb-contents'); 143 this.startTimeEl!.innerHTML = getTimeString(TraceRow.range?.startNS!); 144 this.endTimeEl = this.shadowRoot?.querySelector('.endTime'); 145 this.endTimeEl!.innerHTML = getTimeString(TraceRow.range?.endNS!); 146 147 let rightBarGroup: unknown = [ 148 { 149 barName: '聊天', 150 barEl: this.chatBar, 151 imgEl: this.chatImg, 152 barFlag: 'chat', 153 img: 'img/talk.png', 154 activeImg: 'img/talk_active.png', 155 showPage: this.askQuestion, 156 isMustLoadedTrace: false 157 }, 158 { 159 barName: '诊断', 160 barEl: this.reportBar, 161 imgEl: this.reportImg, 162 barFlag: 'detect', 163 img: 'img/report.png', 164 activeImg: 'img/report_active.png', 165 showPage: this.reportDetails, 166 isMustLoadedTrace: true 167 } 168 ]; 169 170 // 给右边栏添加点击事件 171 // @ts-ignore 172 rightBarGroup.forEach((barItem: unknown, index: number) => { 173 // @ts-ignore 174 barItem.barEl.addEventListener('click', (ev: Event) => { 175 // @ts-ignore 176 if (barItem.isMustLoadedTrace && !SpApplication.isTraceLoaded) { 177 let importTraceTips = '请先导入trace,再使用诊断功能'; 178 this.tipContentArr = ['chat']; 179 this.abnormalPageTips(importTraceTips, '', 4000, this.tipContentArr); 180 return; 181 } 182 // this.tipsContent!.style.display = this.isNodata && barItem.barFlag === 'detect' ? 'flex' : 'none'; 183 this.tipsContainer!.style.display = 'none'; 184 // @ts-ignore 185 this.showPageFlag = barItem.barFlag; 186 // @ts-ignore 187 barItem.imgEl.src = barItem.activeImg; 188 // @ts-ignore 189 barItem.barEl.classList.add('active'); 190 // @ts-ignore 191 barItem.showPage.style.display = 'block'; 192 // @ts-ignore 193 if (this.tipContentArr.indexOf(barItem.barFlag) > -1) { 194 this.tipsContainer!.style.display = 'flex'; 195 } 196 // @ts-ignore 197 for (let i = 0; i < rightBarGroup.length; i++) { 198 if (i !== index) { 199 // @ts-ignore 200 rightBarGroup[i].barEl.classList.remove('active'); 201 // @ts-ignore 202 rightBarGroup[i].imgEl.src = rightBarGroup[i].img; 203 // @ts-ignore 204 rightBarGroup[i].showPage.style.display = 'none'; 205 } 206 } 207 }); 208 }); 209 210 // 发送消息图标点击事件 211 this.sendImg?.addEventListener('click', () => { 212 this.sendMessage(); 213 }); 214 215 // 新建对话按钮点击事件 216 this.newChatEl?.addEventListener('click', () => { 217 this.isNewChat = true; 218 this.isResultBack = true; 219 this.chatToken = ''; 220 this.q_a_window!.innerHTML = ''; 221 this.createAiChatBox('有什么可以帮助您的吗?'); 222 }); 223 224 //通过右上角的‘X’按钮关闭窗口 225 //@ts-ignore 226 closeBtn?.addEventListener('click', () => { 227 //@ts-ignore 228 aiAssistant?.style.visibility = 'hidden'; 229 //@ts-ignore 230 aiAssistant?.style.display = 'none'; 231 }); 232 233 // 输入框发送消息 234 this.inputEl?.addEventListener('keydown', (e) => { 235 if (e.key.toLocaleLowerCase() === 'control' || e.keyCode === 17) { 236 this.isCtrlDown = true; 237 } 238 if (this.isCtrlDown) { 239 if (e.key.toLocaleLowerCase() === 'enter') { 240 this.inputEl!.value += '\n'; 241 } 242 } else { 243 if (e.key.toLocaleLowerCase() === 'enter') { 244 this.sendMessage(); 245 // 禁止默认的回车换行 246 e.preventDefault(); 247 e.stopPropagation(); 248 }; 249 }; 250 }); 251 252 // 输入框聚焦/失焦--防止触发页面快捷键 253 this.inputEl?.addEventListener('focus', () => { 254 SpSystemTrace.isAiAsk = true; 255 }); 256 257 this.inputEl?.addEventListener('blur', () => { 258 SpSystemTrace.isAiAsk = false; 259 }); 260 261 // 监听浏览器刷新,清除db数据 262 window.onbeforeunload = function (): void { 263 caches.delete(`${window.localStorage.getItem('fileName')}.db`); 264 sessionStorage.removeItem('fileName'); 265 }; 266 267 // 监听ctrl抬起 268 this.inputEl?.addEventListener('keyup', (e) => { 269 if (e.key.toLocaleLowerCase() === 'control' || e.keyCode === 17) { 270 this.isCtrlDown = false; 271 }; 272 e.preventDefault(); 273 e.stopPropagation(); 274 }); 275 276 // 下载诊断报告按钮监听 277 this.downloadBtn?.addEventListener('click', () => { 278 let a = document.createElement('a'); 279 a.href = URL.createObjectURL(new Blob([this.reportContent])); 280 a.download = window.sessionStorage.getItem('fileName')! + '诊断报告'; 281 a.click(); 282 }); 283 284 this.draftBtn?.addEventListener('click', async () => { 285 this.draftList!.innerHTML = ''; 286 this.contentsTable!.style.display = 'none'; 287 this.tipsContainer!.style.display = 'none'; 288 this.tipsContent!.style.display = 'none'; 289 this.downloadBtn!.style.display = 'none'; 290 // 清空诊断报告的内容 291 this.reportContent = ''; 292 // 隐藏诊断按钮 293 this.draftBtn!.style.display = 'none'; 294 // 同一个trace非第一次诊断,无需再发db文件过去 295 if (SpAiAnalysisPage.isRepeatedly) { 296 this.initiateDiagnosis(); 297 } else { 298 // 首次诊断 299 WebSocketManager.getInstance()!.registerMessageListener(TypeConstants.DIAGNOSIS_TYPE, this.webSocketCallBack, this.eventCallBack); 300 // 看缓存中有没有db,没有的话拿一个进行诊断并存缓存 301 let fileName = sessionStorage.getItem('fileName'); 302 await caches.match(`/${fileName}.db`).then(response => { 303 if (response) { 304 response.blob().then(blob => { 305 const reader = new FileReader(); 306 reader.readAsArrayBuffer(blob); 307 reader.onloadend = (): void => { 308 const dbBuffer = reader.result; 309 // @ts-ignore 310 const reqBufferDB = new Uint8Array(dbBuffer); 311 // 使用uint8Array 312 WebSocketManager.getInstance()!.sendMessage( 313 TypeConstants.DIAGNOSIS_TYPE, 314 TypeConstants.SENDDB_CMD, 315 reqBufferDB 316 ); 317 }; 318 }); 319 } else { 320 // 如果缓存中没有,从网络获取并存储 321 this.cacheDb(fileName); 322 } 323 }); 324 }; 325 // 点击一键诊断时先挂载loading 326 this.loadingItem = this.loading('style="position:absolute;top:45%;left:45%;z-index:999"'); 327 this.draftList?.appendChild(this.loadingItem!); 328 }); 329 330 // 监听表格目录row点击事件,跳转至对应问题行 331 this.contentsTable!.addEventListener('row-click', (evt) => { 332 // @ts-ignore 333 let index = evt.detail.id; 334 let targets: unknown = this.draftList?.querySelectorAll('.title'); 335 // 目录的(index - 1)对应诊断返回的问题数组下标 336 // @ts-ignore 337 let target = targets[index - 1]; 338 if (target) { 339 // 改变滚动条滚动高度,实现点击目录跳转到对应问题 340 this.shadowRoot!.querySelector('.analysisList')!.scrollTop = target.parentElement.offsetTop - TITLE_HEIGHT; 341 } 342 }); 343 } 344 345 // 加载中的loading模块 346 loading(styleStr: string): HTMLDivElement { 347 let loadingDiv = document.createElement('div'); 348 loadingDiv.className = 'loadingBox'; 349 // 动态渲染Loading的样式 350 loadingDiv.innerHTML = `<lit-loading ${styleStr}></lit-loading>`; 351 let loadingItem = document.createElement('div'); 352 loadingItem.className = 'loadingItem'; 353 loadingItem!.appendChild(loadingDiv); 354 return loadingItem; 355 } 356 357 // 重新导trace、db时,初始化诊断功能 358 clear(): void { 359 this.tipContentArr = []; 360 // 判断是否有上一次未完成的优化建议请求,如果有则断掉 361 if (SpStatisticsHttpUtil.controllersMap.size > 0) { 362 SpStatisticsHttpUtil.isInterrupt = true; 363 this.breakRequest(); 364 } 365 this.contentsTable!.style.display = 'none'; 366 this.draftList!.innerHTML = ''; 367 this.reportContent = ''; 368 this.downloadBtn!.style.display = 'none'; 369 this.draftBtn!.style.display = 'inline-block'; 370 let chatBar = this.shadowRoot?.querySelector('.chatBar'); 371 let reportDetails = this.shadowRoot?.querySelector('.report_details'); 372 this.reportImg!.src = 'img/report.png'; 373 this.chatImg!.src = 'img/talk_active.png'; 374 this.reportBar!.classList.remove('active'); 375 chatBar!.classList.add('active'); 376 //@ts-ignore 377 this.askQuestion!.style.display = 'block'; 378 //@ts-ignore 379 reportDetails!.style.display = 'none'; 380 this.tipsContainer!.style.display = 'none'; 381 this.tipsContent!.style.display = 'flex'; 382 this.isNodata = true; 383 } 384 385 // 发送消息 386 async sendMessage(): Promise<void> { 387 if (!this.isResultBack) { 388 return; 389 } 390 if (this.inputEl!.value !== '') { 391 this.isResultBack = false; 392 if (this.isNewChat) { 393 this.isNewChat = false; 394 } 395 this.question = JSON.parse(JSON.stringify(this.inputEl!.value)); 396 this.createChatBox(); 397 this.createAiChatBox('AI智能分析中...'); 398 this.q_a_window!.scrollTop = this.q_a_window!.scrollHeight; 399 this.answer(); 400 } 401 } 402 403 // ai对话 404 async answer(): Promise<void> { 405 let requestBody = { 406 'inputs': {}, 407 'query': this.question, 408 'response_mode': 'blocking', 409 'conversation_id': '', 410 'user': 'abc-123' 411 }; 412 413 await SpStatisticsHttpUtil.askAi(requestBody, 'difyAsk').then(res => { 414 if (res.status === 200) { 415 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_q&a', []); 416 } 417 this.appendChatContent(res); 418 }).catch(error => { 419 this.appendChatContent(error); 420 }); 421 } 422 423 appendChatContent(response: AiResponse): void { 424 if (!this.isNewChat) { 425 // @ts-ignore 426 this.aiAnswerBox!.firstElementChild!.innerHTML = this.md!.render(response.data); 427 let likeDiv = document.createElement('div'); 428 likeDiv.className = 'likeDiv'; 429 likeDiv.innerHTML = '<lit-like type = "chat"></lit-like>'; 430 this.aiAnswerBox?.appendChild(likeDiv); 431 // 滚动条滚到底部 432 this.q_a_window!.scrollTop = this.q_a_window!.scrollHeight; 433 this.isResultBack = true; 434 } 435 } 436 437 // 创建用户聊天对话气泡 438 createChatBox(): void { 439 // 生成头像 440 let headerDiv = document.createElement('div'); 441 headerDiv.className = 'userHeader headerDiv'; 442 // 生成聊天内容框 443 let newQuestion = document.createElement('div'); 444 newQuestion.className = 'usersay'; 445 // @ts-ignore 446 newQuestion!.innerHTML = this.inputEl!.value; 447 // 单条消息模块,最大的div,包含头像、消息、清除浮动元素 448 let newMessage = document.createElement('div'); 449 newMessage.className = 'usermessage message'; 450 // @ts-ignore 451 this.inputEl!.value = ''; 452 newMessage.appendChild(headerDiv); 453 newMessage.appendChild(newQuestion); 454 let claerDiv = document.createElement('div'); 455 claerDiv.className = 'clear'; 456 newMessage.appendChild(claerDiv); 457 this.q_a_window?.appendChild(newMessage); 458 } 459 460 // 创建ai助手聊天对话气泡 461 createAiChatBox(aiText: string): void { 462 // 生成ai头像 463 let headerDiv = document.createElement('div'); 464 headerDiv.className = 'aiHeader headerDiv'; 465 headerDiv.innerHTML = `<img class='headerImg' src = 'img/logo.png' title=''></img>`; 466 let newQuestion = document.createElement('div'); 467 newQuestion.className = 'systemSay'; 468 // @ts-ignore 469 newQuestion!.innerHTML = `<div>${aiText}</div>`; 470 let newMessage = document.createElement('div'); 471 newMessage.className = 'aiMessage message'; 472 newMessage.appendChild(headerDiv); 473 newMessage.appendChild(newQuestion); 474 let claerDiv = document.createElement('div'); 475 claerDiv.className = 'clear'; 476 this.aiAnswerBox = newQuestion; 477 newMessage.appendChild(claerDiv); 478 this.q_a_window?.appendChild(newMessage); 479 } 480 481 // 页面渲染诊断结果 482 async renderData(dataList: unknown): Promise<void> { 483 //生成表格导航 484 //@ts-ignore 485 this.renderTblNav(dataList); 486 this.renderTblNav(dataList); 487 if (this.detectToken === '') { 488 await this.getToken90Min('takeToken', false); 489 } 490 // @ts-ignore 491 for (let i = 0; i < dataList.length; i++) { 492 let itemDiv = document.createElement('div'); 493 itemDiv.className = 'analysisItem'; 494 // 生成标题 495 let titleDiv = document.createElement('div'); 496 titleDiv.className = 'title item-name'; 497 titleDiv!.innerText = `问题${i + 1}`; 498 // 生成一键置顶 499 let topUp = document.createElement('div'); 500 topUp.className = 'top-up-image'; 501 titleDiv.appendChild(topUp); 502 topUp.addEventListener('click', (e) => { 503 this.shadowRoot!.querySelector('.analysisList')!.scrollTop = 0; 504 }); 505 // 生成类型 506 let typeDiv = document.createElement('div'); 507 typeDiv.className = 'item'; 508 // @ts-ignore 509 typeDiv.innerHTML = `<span class='item-name'>问题类型:</span>${dataList[i].type}`; 510 // 生成时间 511 let timeDiv = document.createElement('div'); 512 timeDiv.className = 'item two timeDiv'; 513 timeDiv!.innerHTML = `<span class='item-name'>发生时间:</span>`; 514 let timeList = new Array(); 515 // @ts-ignore 516 dataList[i].trace_info.forEach((v: unknown, index: number) => { 517 let timeSpan = document.createElement('span'); 518 // @ts-ignore 519 timeSpan.id = v.id; 520 timeSpan.className = 'timeItem'; 521 // @ts-ignore 522 timeSpan.setAttribute('name', v.name); 523 // @ts-ignore 524 timeSpan.innerHTML = `[<span class = 'timeText'>${v.ts! / 1000000000}</span>s]${index !== dataList[i].trace_info.length - 1 ? ' ,' : ''}`; 525 timeDiv.appendChild(timeSpan); 526 // @ts-ignore 527 timeList.push(v.ts! / 1000000000 + 's'); 528 }); 529 // 生成问题原因 530 let reasonDiv = document.createElement('div'); 531 reasonDiv.className = 'item'; 532 // @ts-ignore 533 reasonDiv!.innerHTML = `<span class='item-name'>问题原因:</span>${dataList[i].description}`; 534 itemDiv.appendChild(titleDiv); 535 itemDiv.appendChild(typeDiv); 536 itemDiv.appendChild(timeDiv); 537 itemDiv.appendChild(reasonDiv); 538 this.timeClickHandler(timeDiv); 539 // 生成优化建议 540 let suggestonDiv = document.createElement('div'); 541 suggestonDiv.className = 'item two'; 542 let suggestonTitle = document.createElement('span'); 543 suggestonTitle.className = 'item-name'; 544 suggestonTitle.textContent = '优化建议:'; 545 suggestonDiv!.appendChild(suggestonTitle); 546 itemDiv!.appendChild(suggestonDiv); 547 let getButton = document.createElement('span'); 548 getButton.className = 'getSgtBtn'; 549 getButton.innerHTML = '获取'; 550 getButton.addEventListener('click', (ev) => { 551 if (suggestonDiv.getElementsByClassName('msgdiv').length > 0) { 552 suggestonDiv.removeChild(suggestonDiv.getElementsByClassName('msgdiv')[0]); 553 } 554 if (suggestonDiv.getElementsByClassName('likeDiv').length > 0) { 555 suggestonDiv.removeChild(suggestonDiv.getElementsByClassName('likeDiv')[0]); 556 } 557 for (let i = 0; i < this.getSugBtnList.length; i++) { 558 // @ts-ignore 559 this.getSugBtnList[i].style.display = 'none'; 560 } 561 suggestonDiv!.appendChild(this.loading('')); 562 // @ts-ignore 563 this.getSuggestion(dataList, i, suggestonDiv, timeList); 564 }); 565 suggestonTitle.appendChild(getButton); 566 this.draftList!.insertBefore(itemDiv!, this.loadingItem!); 567 itemDiv!.style.animation = 'opcityliner 3s'; 568 itemDiv!.style.paddingBottom = '20px'; 569 } 570 // @ts-ignore 571 this.getSugBtnList = this.draftList?.getElementsByClassName('getSgtBtn'); 572 // @ts-ignore 573 this.getSugBtnList[0].click(); 574 this.draftList?.removeChild(this.loadingItem!); 575 } 576 577 578 // Table数据渲染 579 renderTblNav(dataList: unknown): void { 580 this.contentsTable!.recycleDataSource = []; 581 this.contentsTable!.style.display = 'block'; 582 // 修改Tbl样式 583 let th = this.contentsTable!.shadowRoot!.querySelector('div.table > div.thead > div')! as HTMLElement; 584 th.style.backgroundColor = '#8bbcdff7'; 585 // @ts-ignore 586 let source = dataList.map((item: unknown, index: number) => { 587 // @ts-ignore 588 return { ...item, id: index + 1 }; 589 }); 590 let tbody = this.contentsTable!.shadowRoot!.querySelector('.table') as HTMLElement; 591 tbody.style.height = 30 + 25 * source.length + 'px'; 592 tbody.style.maxHeight = TBODY_HEIGHT + 'px'; 593 this.contentsTable!.recycleDataSource = source; 594 } 595 596 connectedCallback(): void { 597 super.connectedCallback(); 598 } 599 600 async getToken(params: string, isChat?: boolean): Promise<void> { 601 let data = await SpStatisticsHttpUtil.getAItoken(params); 602 if (data.status !== 200) { 603 if (isChat) { 604 this.aiAnswerBox!.firstElementChild!.innerHTML = '获取token失败'; 605 } 606 return; 607 } else { 608 if (isChat) { 609 this.chatToken = data.data; 610 } else { 611 this.detectToken = data.data; 612 } 613 } 614 } 615 616 //控制页面异常场景的显示 617 abnormalPageTips(tipStr: string, imgSrc: string, setTimeoutTime: number, flag: Array<string>): void { 618 // 清除延时器,防止弹窗重叠互相影响 619 if (this.timerId) { 620 // @ts-ignore 621 clearTimeout(this.timerId); 622 } 623 this.tipsContainer!.innerHTML = ''; 624 if (imgSrc !== '') { 625 let mixedTipsBox = document.createElement('div'); 626 mixedTipsBox.className = 'mixedTips'; 627 let mixedImg = document.createElement('img'); 628 mixedImg.src = imgSrc; 629 let mixedText = document.createElement('div'); 630 mixedText.className = 'mixedText'; 631 mixedText.innerHTML = tipStr; 632 mixedTipsBox.appendChild(mixedImg); 633 mixedTipsBox.appendChild(mixedText); 634 this.tipsContainer!.appendChild(mixedTipsBox); 635 this.tipsContainer!.style.display = 'none'; 636 } else { 637 let textTipsBox = document.createElement('div'); 638 textTipsBox.className = 'textTips'; 639 textTipsBox!.innerHTML = tipStr; 640 this.tipsContainer!.appendChild(textTipsBox); 641 } 642 if (flag.indexOf(this.showPageFlag) > -1) { 643 this.tipsContainer!.style.display = 'flex'; 644 } 645 if (setTimeoutTime) { 646 setTimeout(() => { 647 this.timerId = this.tipsContainer!.style.display = 'none'; 648 this.tipContentArr = []; 649 }, setTimeoutTime); 650 } 651 } 652 653 // 每90min重新获取token 654 async getToken90Min(params: string, isChat: boolean): Promise<void> { 655 await this.getToken(params, isChat); 656 setInterval(async () => { 657 await this.getToken(params, isChat); 658 }, 5400000); 659 } 660 661 // 发送请求获取优化建议并渲染页面 662 getSuggestion( 663 dataList: unknown, 664 i: number, 665 suggestonDiv: HTMLDivElement | null | undefined, 666 timeList: Array<string> 667 ): void { 668 SpStatisticsHttpUtil.askAi({ 669 token: this.detectToken, 670 // @ts-ignore 671 question: dataList[i].description + ',请问该怎么优化?', 672 collection: '' 673 }, 'ask').then((suggestion) => { 674 this.appendMsg(dataList, i, suggestonDiv, timeList, suggestion); 675 }).catch((error) => { 676 this.appendMsg(dataList, i, suggestonDiv, timeList, error); 677 }); 678 } 679 680 // 优化建议msg处理并渲染 681 appendMsg(dataList: unknown, i: number, suggestonDiv: HTMLDivElement | null | undefined, timeList: Array<string>, suggestion: AiResponse): void { 682 // 保证controllersMap里面存的是未完成的请求,并规避重新打开trace时异步msg未请求完毕引入的问题 683 if (SpStatisticsHttpUtil.controllersMap.has(suggestion.time!)) { 684 SpStatisticsHttpUtil.controllersMap.delete(suggestion.time!); 685 } 686 if (SpStatisticsHttpUtil.isInterrupt) { 687 if (SpStatisticsHttpUtil.controllersMap.size === 0) { 688 SpStatisticsHttpUtil.isInterrupt = false; 689 } 690 return; 691 } 692 // @ts-ignore 693 this.reportContent += `问题${i + 1}:${dataList[i].type}\n\n时间:${timeList.join(',')}\n\n问题原因:${dataList[i].description}\n\n优化建议:${suggestion.data}\n\n\n`; 694 let msgdiv = document.createElement('div'); 695 msgdiv.className = 'msgdiv'; 696 //@ts-ignore 697 msgdiv!.innerHTML = `${this.md!.render(suggestion.data)}`; 698 suggestonDiv?.removeChild(suggestonDiv.lastElementChild!); 699 let likeDiv = document.createElement('div'); 700 likeDiv.className = 'likeDiv'; 701 // @ts-ignore 702 likeDiv.innerHTML = `<lit-like type = "detect" content = ${dataList[i].type}#${dataList[i].subtype}></lit-like>`; 703 suggestonDiv!.appendChild(msgdiv); 704 suggestonDiv!.appendChild(likeDiv); 705 for (let i = 0; i < this.getSugBtnList.length; i++) { 706 // @ts-ignore 707 this.getSugBtnList[i].style.display = 'inline-block'; 708 } 709 } 710 711 // 取消或中断请求 712 breakRequest(): void { 713 for (const controller of SpStatisticsHttpUtil.controllersMap.values()) { 714 controller.abort(); 715 } 716 } 717 718 cacheDb(fileName: string | null): void { 719 threadPool.submit( 720 'download-db', 721 '', 722 {}, 723 (reqBufferDB: Uint8Array) => { 724 WebSocketManager.getInstance()!.sendMessage(TypeConstants.DIAGNOSIS_TYPE, TypeConstants.SENDDB_CMD, reqBufferDB); 725 // 存入缓存 726 // @ts-ignore 727 const blob = new Blob([reqBufferDB]); 728 const response = new Response(blob); 729 caches.open('DB-file').then(cache => { 730 return cache.put(`/${fileName}.db`, response); 731 }); 732 }, 733 'download-db' 734 ); 735 } 736 737 // websocket通信回调注册 738 // @ts-ignore 739 webSocketCallBack = async (cmd: number, result: Uint8Array): unknown => { 740 const decoder = new TextDecoder(); 741 const jsonString = decoder.decode(result); 742 let jsonRes = JSON.parse(jsonString); 743 // db文件写入成功 744 if (cmd === 2) { 745 SpAiAnalysisPage.isRepeatedly = true; 746 this.initiateDiagnosis(); 747 if (jsonRes.resultCode !== 0) { 748 this.draftBtn!.style.display = 'inline-block'; 749 } 750 } 751 if (cmd === 4) { 752 // 需要处理 753 if (jsonRes.resultCode !== 0) { 754 this.isNodata = true; 755 this.draftList!.innerHTML = ''; 756 this.contentsTable!.style.display = 'none'; 757 let textStr = '服务异常,请重新导trace!'; 758 let imgsrc = 'img/no-report.png'; 759 this.tipsContent!.style.display = 'none'; 760 this.tipContentArr = ['detect']; 761 this.abnormalPageTips(textStr, imgsrc, 0, this.tipContentArr); 762 this.draftBtn!.style.display = 'inline-block'; 763 } 764 if (this.isJsonString(jsonRes.resultMessage)) { 765 let dataList = JSON.parse(jsonRes.resultMessage) || []; 766 if (dataList && dataList.length === 0) { 767 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_detect', ['0']); 768 this.isNodata = true; 769 this.draftList!.innerHTML = ''; 770 this.contentsTable!.style.display = 'none'; 771 let textStr = '当前未诊断出问题'; 772 let imgsrc = 'img/no-report.png'; 773 this.tipsContent!.style.display = 'none'; 774 this.tipContentArr = ['detect']; 775 this.abnormalPageTips(textStr, imgsrc, 0, this.tipContentArr); 776 this.draftBtn!.style.display = 'inline-block'; 777 } else { 778 this.tipContentArr = []; 779 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_detect', ['1']); 780 this.isNodata = false; 781 // 整理数据,渲染数据 782 await this.renderData(dataList); 783 this.draftBtn!.style.display = 'inline-block'; 784 this.downloadBtn!.style.display = 'inline-block'; 785 } 786 }; 787 }; 788 }; 789 790 // eventCallBack 791 eventCallBack = async (result: string): Promise<void> => { 792 this.draftList!.innerHTML = ''; 793 this.tipsContent!.style.display = 'flex'; 794 this.tipContentArr = ['detect']; 795 // @ts-ignore 796 this.abnormalPageTips(this.getStatusesPrompt()[result].prompt, '', 4000, ['detect']); 797 this.draftBtn!.style.display = 'inline-block'; 798 }; 799 800 // 发起诊断 801 initiateDiagnosis(): void { 802 let requestBodyObj = { 803 startTime: Math.round(SpAiAnalysisPage.startTime + Utils.getInstance().getRecordStartNS()), 804 endTime: Math.round(SpAiAnalysisPage.endTime + Utils.getInstance().getRecordStartNS()) 805 }; 806 let requestBodyString = JSON.stringify(requestBodyObj); 807 let requestBody = new TextEncoder().encode(requestBodyString); 808 WebSocketManager.getInstance()!.sendMessage(TypeConstants.DIAGNOSIS_TYPE, TypeConstants.DIAGNOSIS_CMD, requestBody); 809 } 810 811 // 点击时间跳转 812 timeClickHandler(timeDiv: HTMLDivElement): void { 813 let timeElementList = timeDiv!.getElementsByClassName('timeItem'); 814 for (let i = 0; i < timeElementList.length; i++) { 815 timeElementList[i].addEventListener('click', (e) => { 816 if (this.activeTime) { 817 this.activeTime.removeAttribute('active'); 818 } 819 this.activeTime = timeElementList[i].getElementsByClassName('timeText')[0]; 820 // 点击项更换颜色 821 this.activeTime.setAttribute('active', ''); 822 let name = timeElementList[i].getAttribute('name'); 823 let id = Number(timeElementList[i].getAttribute('id')); 824 // @ts-ignore 825 this.valueChangeHandler!(name, id); 826 }); 827 } 828 } 829 830 // 判断是否为json 831 isJsonString(str: string): boolean { 832 try { 833 JSON.parse(str); 834 } catch (e) { 835 return false; 836 } 837 return true; 838 } 839 840 // 获取提示语 841 getStatusesPrompt(): unknown { 842 let guideSrc = `https://${window.location.host.split(':')[0]}:${window.location.port 843 }${window.location.pathname}?action=help_27`; 844 return { 845 unconnected: { 846 prompt: `未连接,请启动本地扩展程序再试![</span><a href=${guideSrc} style="color: blue;" target="_blank">指导</a><span>]` 847 }, // 重连 848 connected: { 849 prompt: '扩展程序连接中,请稍后再试!' 850 }, // 中间 851 logined: { 852 prompt: '扩展程序连接中,请稍后再试!' 853 }, // 中间 854 loginFailedByLackSession: { 855 prompt: '当前所有会话都在使用中,请释放一些会话再试!' 856 }, // 重连 857 upgrading: { 858 prompt: '扩展程序连接中,请稍后再试!' 859 }, // 中间 860 upgradeSuccess: { 861 prompt: '扩展程序已完成升级,重启中,请稍后再试!' 862 }, // 重连 863 upgradeFailed: { 864 prompt: '刷新页面触发升级,或卸载扩展程序重装!' 865 }, // 重连 866 }; 867 } 868 869 initHtml(): string { 870 return SpAiAnalysisPageHtml; 871 } 872}