• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}