• 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            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}