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 }); 273 274 // 下载诊断报告按钮监听 275 this.downloadBtn?.addEventListener('click', () => { 276 let a = document.createElement('a'); 277 a.href = URL.createObjectURL(new Blob([this.reportContent])); 278 a.download = window.sessionStorage.getItem('fileName')! + '诊断报告'; 279 a.click(); 280 }); 281 282 this.draftBtn?.addEventListener('click', async () => { 283 this.draftList!.innerHTML = ''; 284 this.contentsTable!.style.display = 'none'; 285 this.tipsContainer!.style.display = 'none'; 286 this.tipsContent!.style.display = 'none'; 287 this.downloadBtn!.style.display = 'none'; 288 // 清空诊断报告的内容 289 this.reportContent = ''; 290 // 隐藏诊断按钮 291 this.draftBtn!.style.display = 'none'; 292 // 同一个trace非第一次诊断,无需再发db文件过去 293 if (SpAiAnalysisPage.isRepeatedly) { 294 this.initiateDiagnosis(); 295 } else { 296 // 首次诊断 297 WebSocketManager.getInstance()!.registerMessageListener(TypeConstants.DIAGNOSIS_TYPE, this.webSocketCallBack, this.eventCallBack); 298 // 看缓存中有没有db,没有的话拿一个进行诊断并存缓存 299 let fileName = sessionStorage.getItem('fileName'); 300 await caches.match(`/${fileName}.db`).then(response => { 301 if (response) { 302 response.blob().then(blob => { 303 const reader = new FileReader(); 304 reader.readAsArrayBuffer(blob); 305 reader.onloadend = (): void => { 306 const dbBuffer = reader.result; 307 // @ts-ignore 308 const reqBufferDB = new Uint8Array(dbBuffer); 309 // 使用uint8Array 310 WebSocketManager.getInstance()!.sendMessage( 311 TypeConstants.DIAGNOSIS_TYPE, 312 TypeConstants.SENDDB_CMD, 313 reqBufferDB 314 ); 315 }; 316 }); 317 } else { 318 // 如果缓存中没有,从网络获取并存储 319 this.cacheDb(fileName); 320 } 321 }); 322 }; 323 // 点击一键诊断时先挂载loading 324 this.loadingItem = this.loading('style="position:absolute;top:45%;left:45%;z-index:999"'); 325 this.draftList?.appendChild(this.loadingItem!); 326 }); 327 328 // 监听表格目录row点击事件,跳转至对应问题行 329 this.contentsTable!.addEventListener('row-click', (evt) => { 330 // @ts-ignore 331 let index = evt.detail.id; 332 let targets: unknown = this.draftList?.querySelectorAll('.title'); 333 // 目录的(index - 1)对应诊断返回的问题数组下标 334 // @ts-ignore 335 let target = targets[index - 1]; 336 if (target) { 337 // 改变滚动条滚动高度,实现点击目录跳转到对应问题 338 this.shadowRoot!.querySelector('.analysisList')!.scrollTop = target.parentElement.offsetTop - TITLE_HEIGHT; 339 } 340 }); 341 } 342 343 // 加载中的loading模块 344 loading(styleStr: string): HTMLDivElement { 345 let loadingDiv = document.createElement('div'); 346 loadingDiv.className = 'loadingBox'; 347 // 动态渲染Loading的样式 348 loadingDiv.innerHTML = `<lit-loading ${styleStr}></lit-loading>`; 349 let loadingItem = document.createElement('div'); 350 loadingItem.className = 'loadingItem'; 351 loadingItem!.appendChild(loadingDiv); 352 return loadingItem; 353 } 354 355 // 重新导trace、db时,初始化诊断功能 356 clear(): void { 357 this.tipContentArr = []; 358 // 判断是否有上一次未完成的优化建议请求,如果有则断掉 359 if (SpStatisticsHttpUtil.controllersMap.size > 0) { 360 SpStatisticsHttpUtil.isInterrupt = true; 361 this.breakRequest(); 362 } 363 this.contentsTable!.style.display = 'none'; 364 this.draftList!.innerHTML = ''; 365 this.reportContent = ''; 366 this.downloadBtn!.style.display = 'none'; 367 this.draftBtn!.style.display = 'inline-block'; 368 let chatBar = this.shadowRoot?.querySelector('.chatBar'); 369 let reportDetails = this.shadowRoot?.querySelector('.report_details'); 370 this.reportImg!.src = 'img/report.png'; 371 this.chatImg!.src = 'img/talk_active.png'; 372 this.reportBar!.classList.remove('active'); 373 chatBar!.classList.add('active'); 374 //@ts-ignore 375 this.askQuestion!.style.display = 'block'; 376 //@ts-ignore 377 reportDetails!.style.display = 'none'; 378 this.tipsContainer!.style.display = 'none'; 379 this.tipsContent!.style.display = 'flex'; 380 this.isNodata = true; 381 } 382 383 // 发送消息 384 async sendMessage(): Promise<void> { 385 if (!this.isResultBack) { 386 return; 387 } 388 if (this.inputEl!.value !== '') { 389 this.isResultBack = false; 390 if (this.isNewChat) { 391 this.isNewChat = false; 392 } 393 this.question = JSON.parse(JSON.stringify(this.inputEl!.value)); 394 this.createChatBox(); 395 this.createAiChatBox('AI智能分析中...'); 396 this.q_a_window!.scrollTop = this.q_a_window!.scrollHeight; 397 // 没有token 398 if (this.chatToken === '') { 399 await this.getToken90Min('aiTakeToken', true); 400 } 401 if (this.chatToken !== '') { 402 this.answer(); 403 } 404 } 405 } 406 407 // ai对话 408 async answer(): Promise<void> { 409 let requestBody = { 410 token: this.chatToken, 411 question: this.question, 412 collection: 'smart_perf_test', 413 scope: 'smartperf' 414 }; 415 416 await SpStatisticsHttpUtil.askAi(requestBody, 'aiAsk').then(res => { 417 if (res.status === 200) { 418 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_q&a', []); 419 } 420 this.appendChatContent(res); 421 }).catch(error => { 422 this.appendChatContent(error); 423 }); 424 } 425 426 appendChatContent(response: AiResponse): void { 427 if (!this.isNewChat) { 428 // @ts-ignore 429 this.aiAnswerBox!.firstElementChild!.innerHTML = this.md!.render(response.data); 430 let likeDiv = document.createElement('div'); 431 likeDiv.className = 'likeDiv'; 432 likeDiv.innerHTML = '<lit-like type = "chat"></lit-like>'; 433 this.aiAnswerBox?.appendChild(likeDiv); 434 // 滚动条滚到底部 435 this.q_a_window!.scrollTop = this.q_a_window!.scrollHeight; 436 this.isResultBack = true; 437 } 438 } 439 440 // 创建用户聊天对话气泡 441 createChatBox(): void { 442 // 生成头像 443 let headerDiv = document.createElement('div'); 444 headerDiv.className = 'userHeader headerDiv'; 445 // 生成聊天内容框 446 let newQuestion = document.createElement('div'); 447 newQuestion.className = 'usersay'; 448 // @ts-ignore 449 newQuestion!.innerHTML = this.inputEl!.value; 450 // 单条消息模块,最大的div,包含头像、消息、清除浮动元素 451 let newMessage = document.createElement('div'); 452 newMessage.className = 'usermessage message'; 453 // @ts-ignore 454 this.inputEl!.value = ''; 455 newMessage.appendChild(headerDiv); 456 newMessage.appendChild(newQuestion); 457 let claerDiv = document.createElement('div'); 458 claerDiv.className = 'clear'; 459 newMessage.appendChild(claerDiv); 460 this.q_a_window?.appendChild(newMessage); 461 } 462 463 // 创建ai助手聊天对话气泡 464 createAiChatBox(aiText: string): void { 465 // 生成ai头像 466 let headerDiv = document.createElement('div'); 467 headerDiv.className = 'aiHeader headerDiv'; 468 headerDiv.innerHTML = `<img class='headerImg' src = 'img/logo.png' title=''></img>`; 469 let newQuestion = document.createElement('div'); 470 newQuestion.className = 'systemSay'; 471 // @ts-ignore 472 newQuestion!.innerHTML = `<div>${aiText}</div>`; 473 let newMessage = document.createElement('div'); 474 newMessage.className = 'aiMessage message'; 475 newMessage.appendChild(headerDiv); 476 newMessage.appendChild(newQuestion); 477 let claerDiv = document.createElement('div'); 478 claerDiv.className = 'clear'; 479 this.aiAnswerBox = newQuestion; 480 newMessage.appendChild(claerDiv); 481 this.q_a_window?.appendChild(newMessage); 482 } 483 484 // 页面渲染诊断结果 485 async renderData(dataList: unknown): Promise<void> { 486 //生成表格导航 487 //@ts-ignore 488 this.renderTblNav(dataList); 489 this.renderTblNav(dataList); 490 if (this.detectToken === '') { 491 await this.getToken90Min('takeToken', false); 492 } 493 // @ts-ignore 494 for (let i = 0; i < dataList.length; i++) { 495 let itemDiv = document.createElement('div'); 496 itemDiv.className = 'analysisItem'; 497 // 生成标题 498 let titleDiv = document.createElement('div'); 499 titleDiv.className = 'title item-name'; 500 titleDiv!.innerText = `问题${i + 1}`; 501 // 生成一键置顶 502 let topUp = document.createElement('div'); 503 topUp.className = 'top-up-image'; 504 titleDiv.appendChild(topUp); 505 topUp.addEventListener('click', (e) => { 506 this.shadowRoot!.querySelector('.analysisList')!.scrollTop = 0; 507 }); 508 // 生成类型 509 let typeDiv = document.createElement('div'); 510 typeDiv.className = 'item'; 511 // @ts-ignore 512 typeDiv.innerHTML = `<span class='item-name'>问题类型:</span>${dataList[i].type}`; 513 // 生成时间 514 let timeDiv = document.createElement('div'); 515 timeDiv.className = 'item two timeDiv'; 516 timeDiv!.innerHTML = `<span class='item-name'>发生时间:</span>`; 517 let timeList = new Array(); 518 // @ts-ignore 519 dataList[i].trace_info.forEach((v: unknown, index: number) => { 520 let timeSpan = document.createElement('span'); 521 // @ts-ignore 522 timeSpan.id = v.id; 523 timeSpan.className = 'timeItem'; 524 // @ts-ignore 525 timeSpan.setAttribute('name', v.name); 526 // @ts-ignore 527 timeSpan.innerHTML = `[<span class = 'timeText'>${v.ts! / 1000000000}</span>s]${index !== dataList[i].trace_info.length - 1 ? ' ,' : ''}`; 528 timeDiv.appendChild(timeSpan); 529 // @ts-ignore 530 timeList.push(v.ts! / 1000000000 + 's'); 531 }); 532 // 生成问题原因 533 let reasonDiv = document.createElement('div'); 534 reasonDiv.className = 'item'; 535 // @ts-ignore 536 reasonDiv!.innerHTML = `<span class='item-name'>问题原因:</span>${dataList[i].description}`; 537 itemDiv.appendChild(titleDiv); 538 itemDiv.appendChild(typeDiv); 539 itemDiv.appendChild(timeDiv); 540 itemDiv.appendChild(reasonDiv); 541 this.timeClickHandler(timeDiv); 542 // 生成优化建议 543 let suggestonDiv = document.createElement('div'); 544 suggestonDiv.className = 'item two'; 545 let suggestonTitle = document.createElement('span'); 546 suggestonTitle.className = 'item-name'; 547 suggestonTitle.textContent = '优化建议:'; 548 suggestonDiv!.appendChild(suggestonTitle); 549 itemDiv!.appendChild(suggestonDiv); 550 let getButton = document.createElement('span'); 551 getButton.className = 'getSgtBtn'; 552 getButton.innerHTML = '获取'; 553 getButton.addEventListener('click', (ev) => { 554 if (suggestonDiv.getElementsByClassName('msgdiv').length > 0) { 555 suggestonDiv.removeChild(suggestonDiv.getElementsByClassName('msgdiv')[0]); 556 } 557 if (suggestonDiv.getElementsByClassName('likeDiv').length > 0) { 558 suggestonDiv.removeChild(suggestonDiv.getElementsByClassName('likeDiv')[0]); 559 } 560 for (let i = 0; i < this.getSugBtnList.length; i++) { 561 // @ts-ignore 562 this.getSugBtnList[i].style.display = 'none'; 563 } 564 suggestonDiv!.appendChild(this.loading('')); 565 // @ts-ignore 566 this.getSuggestion(dataList, i, suggestonDiv, timeList); 567 }); 568 suggestonTitle.appendChild(getButton); 569 this.draftList!.insertBefore(itemDiv!, this.loadingItem!); 570 itemDiv!.style.animation = 'opcityliner 3s'; 571 itemDiv!.style.paddingBottom = '20px'; 572 } 573 // @ts-ignore 574 this.getSugBtnList = this.draftList?.getElementsByClassName('getSgtBtn'); 575 // @ts-ignore 576 this.getSugBtnList[0].click(); 577 this.draftList?.removeChild(this.loadingItem!); 578 } 579 580 581 // Table数据渲染 582 renderTblNav(dataList: unknown): void { 583 this.contentsTable!.recycleDataSource = []; 584 this.contentsTable!.style.display = 'block'; 585 // 修改Tbl样式 586 let th = this.contentsTable!.shadowRoot!.querySelector('div.table > div.thead > div')! as HTMLElement; 587 th.style.backgroundColor = '#8bbcdff7'; 588 // @ts-ignore 589 let source = dataList.map((item: unknown, index: number) => { 590 // @ts-ignore 591 return { ...item, id: index + 1 }; 592 }); 593 let tbody = this.contentsTable!.shadowRoot!.querySelector('.table') as HTMLElement; 594 tbody.style.height = 30 + 25 * source.length + 'px'; 595 tbody.style.maxHeight = TBODY_HEIGHT + 'px'; 596 this.contentsTable!.recycleDataSource = source; 597 } 598 599 connectedCallback(): void { 600 super.connectedCallback(); 601 } 602 603 async getToken(params: string, isChat?: boolean): Promise<void> { 604 let data = await SpStatisticsHttpUtil.getAItoken(params); 605 if (data.status !== 200) { 606 if (isChat) { 607 this.aiAnswerBox!.firstElementChild!.innerHTML = '获取token失败'; 608 } 609 return; 610 } else { 611 if (isChat) { 612 this.chatToken = data.data; 613 } else { 614 this.detectToken = data.data; 615 } 616 } 617 } 618 619 //控制页面异常场景的显示 620 abnormalPageTips(tipStr: string, imgSrc: string, setTimeoutTime: number, flag: Array<string>): void { 621 // 清除延时器,防止弹窗重叠互相影响 622 if (this.timerId) { 623 // @ts-ignore 624 clearTimeout(this.timerId); 625 } 626 this.tipsContainer!.innerHTML = ''; 627 if (imgSrc !== '') { 628 let mixedTipsBox = document.createElement('div'); 629 mixedTipsBox.className = 'mixedTips'; 630 let mixedImg = document.createElement('img'); 631 mixedImg.src = imgSrc; 632 let mixedText = document.createElement('div'); 633 mixedText.className = 'mixedText'; 634 mixedText.innerHTML = tipStr; 635 mixedTipsBox.appendChild(mixedImg); 636 mixedTipsBox.appendChild(mixedText); 637 this.tipsContainer!.appendChild(mixedTipsBox); 638 this.tipsContainer!.style.display = 'none'; 639 } else { 640 let textTipsBox = document.createElement('div'); 641 textTipsBox.className = 'textTips'; 642 textTipsBox!.innerHTML = tipStr; 643 this.tipsContainer!.appendChild(textTipsBox); 644 } 645 if (flag.indexOf(this.showPageFlag) > -1) { 646 this.tipsContainer!.style.display = 'flex'; 647 } 648 if (setTimeoutTime) { 649 setTimeout(() => { 650 this.timerId = this.tipsContainer!.style.display = 'none'; 651 this.tipContentArr = []; 652 }, setTimeoutTime); 653 } 654 } 655 656 // 每90min重新获取token 657 async getToken90Min(params: string, isChat: boolean): Promise<void> { 658 await this.getToken(params, isChat); 659 setInterval(async () => { 660 await this.getToken(params, isChat); 661 }, 5400000); 662 } 663 664 // 发送请求获取优化建议并渲染页面 665 getSuggestion( 666 dataList: unknown, 667 i: number, 668 suggestonDiv: HTMLDivElement | null | undefined, 669 timeList: Array<string> 670 ): void { 671 SpStatisticsHttpUtil.askAi({ 672 token: this.detectToken, 673 // @ts-ignore 674 question: dataList[i].description + ',请问该怎么优化?', 675 collection: '' 676 }, 'ask').then((suggestion) => { 677 this.appendMsg(dataList, i, suggestonDiv, timeList, suggestion); 678 }).catch((error) => { 679 this.appendMsg(dataList, i, suggestonDiv, timeList, error); 680 }); 681 } 682 683 // 优化建议msg处理并渲染 684 appendMsg(dataList: unknown, i: number, suggestonDiv: HTMLDivElement | null | undefined, timeList: Array<string>, suggestion: AiResponse): void { 685 // 保证controllersMap里面存的是未完成的请求,并规避重新打开trace时异步msg未请求完毕引入的问题 686 if (SpStatisticsHttpUtil.controllersMap.has(suggestion.time!)) { 687 SpStatisticsHttpUtil.controllersMap.delete(suggestion.time!); 688 } 689 if (SpStatisticsHttpUtil.isInterrupt) { 690 if (SpStatisticsHttpUtil.controllersMap.size === 0) { 691 SpStatisticsHttpUtil.isInterrupt = false; 692 } 693 return; 694 } 695 // @ts-ignore 696 this.reportContent += `问题${i + 1}:${dataList[i].type}\n\n时间:${timeList.join(',')}\n\n问题原因:${dataList[i].description}\n\n优化建议:${suggestion.data}\n\n\n`; 697 let msgdiv = document.createElement('div'); 698 msgdiv.className = 'msgdiv'; 699 //@ts-ignore 700 msgdiv!.innerHTML = `${this.md!.render(suggestion.data)}`; 701 suggestonDiv?.removeChild(suggestonDiv.lastElementChild!); 702 let likeDiv = document.createElement('div'); 703 likeDiv.className = 'likeDiv'; 704 // @ts-ignore 705 likeDiv.innerHTML = `<lit-like type = "detect" content = ${dataList[i].type}#${dataList[i].subtype}></lit-like>`; 706 suggestonDiv!.appendChild(msgdiv); 707 suggestonDiv!.appendChild(likeDiv); 708 for (let i = 0; i < this.getSugBtnList.length; i++) { 709 // @ts-ignore 710 this.getSugBtnList[i].style.display = 'inline-block'; 711 } 712 } 713 714 // 取消或中断请求 715 breakRequest(): void { 716 for (const controller of SpStatisticsHttpUtil.controllersMap.values()) { 717 controller.abort(); 718 } 719 } 720 721 cacheDb(fileName: string | null): void { 722 threadPool.submit( 723 'download-db', 724 '', 725 {}, 726 (reqBufferDB: Uint8Array) => { 727 WebSocketManager.getInstance()!.sendMessage(TypeConstants.DIAGNOSIS_TYPE, TypeConstants.SENDDB_CMD, reqBufferDB); 728 // 存入缓存 729 const blob = new Blob([reqBufferDB]); 730 const response = new Response(blob); 731 caches.open('DB-file').then(cache => { 732 return cache.put(`/${fileName}.db`, response); 733 }); 734 }, 735 'download-db' 736 ); 737 } 738 739 // websocket通信回调注册 740 // @ts-ignore 741 webSocketCallBack = async (cmd: number, result: Uint8Array): unknown => { 742 const decoder = new TextDecoder(); 743 const jsonString = decoder.decode(result); 744 let jsonRes = JSON.parse(jsonString); 745 // db文件写入成功 746 if (cmd === 2) { 747 SpAiAnalysisPage.isRepeatedly = true; 748 this.initiateDiagnosis(); 749 if (jsonRes.resultCode !== 0) { 750 this.draftBtn!.style.display = 'inline-block'; 751 } 752 } 753 if (cmd === 4) { 754 // 需要处理 755 if (jsonRes.resultCode !== 0) { 756 this.isNodata = true; 757 this.draftList!.innerHTML = ''; 758 this.contentsTable!.style.display = 'none'; 759 let textStr = '服务异常,请重新导trace!'; 760 let imgsrc = 'img/no-report.png'; 761 this.tipsContent!.style.display = 'none'; 762 this.tipContentArr = ['detect']; 763 this.abnormalPageTips(textStr, imgsrc, 0, this.tipContentArr); 764 this.draftBtn!.style.display = 'inline-block'; 765 } 766 if (this.isJsonString(jsonRes.resultMessage)) { 767 let dataList = JSON.parse(jsonRes.resultMessage) || []; 768 if (dataList && dataList.length === 0) { 769 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_detect', ['0']); 770 this.isNodata = true; 771 this.draftList!.innerHTML = ''; 772 this.contentsTable!.style.display = 'none'; 773 let textStr = '当前未诊断出问题'; 774 let imgsrc = 'img/no-report.png'; 775 this.tipsContent!.style.display = 'none'; 776 this.tipContentArr = ['detect']; 777 this.abnormalPageTips(textStr, imgsrc, 0, this.tipContentArr); 778 this.draftBtn!.style.display = 'inline-block'; 779 } else { 780 this.tipContentArr = []; 781 SpStatisticsHttpUtil.generalRecord('AI_statistic', 'large_model_detect', ['1']); 782 this.isNodata = false; 783 // 整理数据,渲染数据 784 await this.renderData(dataList); 785 this.draftBtn!.style.display = 'inline-block'; 786 this.downloadBtn!.style.display = 'inline-block'; 787 } 788 }; 789 }; 790 }; 791 792 // eventCallBack 793 eventCallBack = async (result: string): Promise<void> => { 794 this.draftList!.innerHTML = ''; 795 this.tipsContent!.style.display = 'flex'; 796 this.tipContentArr = ['detect']; 797 // @ts-ignore 798 this.abnormalPageTips(this.getStatusesPrompt()[result].prompt, '', 4000, ['detect']); 799 this.draftBtn!.style.display = 'inline-block'; 800 }; 801 802 // 发起诊断 803 initiateDiagnosis(): void { 804 let requestBodyObj = { 805 startTime: Math.round(SpAiAnalysisPage.startTime + Utils.getInstance().getRecordStartNS()), 806 endTime: Math.round(SpAiAnalysisPage.endTime + Utils.getInstance().getRecordStartNS()) 807 }; 808 let requestBodyString = JSON.stringify(requestBodyObj); 809 let requestBody = new TextEncoder().encode(requestBodyString); 810 WebSocketManager.getInstance()!.sendMessage(TypeConstants.DIAGNOSIS_TYPE, TypeConstants.DIAGNOSIS_CMD, requestBody); 811 } 812 813 // 点击时间跳转 814 timeClickHandler(timeDiv: HTMLDivElement): void { 815 let timeElementList = timeDiv!.getElementsByClassName('timeItem'); 816 for (let i = 0; i < timeElementList.length; i++) { 817 timeElementList[i].addEventListener('click', (e) => { 818 if (this.activeTime) { 819 this.activeTime.removeAttribute('active'); 820 } 821 this.activeTime = timeElementList[i].getElementsByClassName('timeText')[0]; 822 // 点击项更换颜色 823 this.activeTime.setAttribute('active', ''); 824 let name = timeElementList[i].getAttribute('name'); 825 let id = Number(timeElementList[i].getAttribute('id')); 826 // @ts-ignore 827 this.valueChangeHandler!(name, id); 828 }); 829 } 830 } 831 832 // 判断是否为json 833 isJsonString(str: string): boolean { 834 try { 835 JSON.parse(str); 836 } catch (e) { 837 return false; 838 } 839 return true; 840 } 841 842 // 获取提示语 843 getStatusesPrompt(): unknown { 844 let guideSrc = `https://${window.location.host.split(':')[0]}:${window.location.port 845 }/application/?action=help_27`; 846 return { 847 unconnected: { 848 prompt: `未连接,请启动本地扩展程序再试![</span><a href=${guideSrc} style="color: blue;" target="_blank">指导</a><span>]` 849 }, // 重连 850 connected: { 851 prompt: '扩展程序连接中,请稍后再试!' 852 }, // 中间 853 logined: { 854 prompt: '扩展程序连接中,请稍后再试!' 855 }, // 中间 856 loginFailedByLackSession: { 857 prompt: '当前所有会话都在使用中,请释放一些会话再试!' 858 }, // 重连 859 upgrading: { 860 prompt: '扩展程序连接中,请稍后再试!' 861 }, // 中间 862 upgradeSuccess: { 863 prompt: '扩展程序已完成升级,重启中,请稍后再试!' 864 }, // 重连 865 upgradeFailed: { 866 prompt: '刷新页面触发升级,或卸载扩展程序重装!' 867 }, // 重连 868 }; 869 } 870 871 initHtml(): string { 872 return SpAiAnalysisPageHtml; 873 } 874}