<template>
  <div class="intelligentQA">
    <div class="history">
      <div class="historyTitle">历史记录</div>
      <div class="historyContent">
        <div v-for="item in historyData" :key="item.id" class="historyItem">
          <span class="question"> {{ item.question }}</span>
          <i class="el-icon-delete historyMore" style="margin-left: 10px" @click="deleteHistory(item.id)"></i>
          <i class="el-icon-more historyMore" @click="historyDetail(item.id)"></i>
        </div>
      </div>
    </div>
    <div class="chat">
      <!-- <div class="header">Header</div> -->
      <div ref="main" class="main">
        <div class="chatLine">
          <div v-for="(item, index) in chatInfo" :key="index">
            <div class="chatRow chatQ" v-if="item.question">
              <div class="questionContent">{{ item.question }}</div>
            </div>
            <!-- 回答 -->
            <div v-else class="chatRow chatA">
              <!--提示 -->
              <div v-if="item.answer" class="tipAnswer">
                {{ item.answer }}
              </div>
              <!-- 图谱数据库 -->
              <div class="answerData" v-if="item.graphAnswer">
                <h2>图谱数据库</h2>
                <div class="answer">{{ item.graphAnswer.answer }}</div>
                <div class="graph">
                  <graphECharts v-if="item.graphAnswer.graph" :graphData="item.graphAnswer.graph" class="charts"></graphECharts>
                  <i v-if="item.userId && item.graphAnswer.ossId" class="el-icon-more more" @click="handleMore(item)"></i>
                </div>
              </div>
              <!-- 知识库 -->
              <div class="answerData" v-if="item.llmAnswer">
                <h2>知识库</h2>
                <div class="answer">
                  <el-collapse v-model="collapseActiveNames">
                    <el-collapse-item title="知识库匹配结果" :name="index">
                      <div v-for="(item, index) in item.llmAnswer.docs" :key="index">
                        <div class="markdown" v-html="renderMarkdown(item)"></div>
                      </div>
                    </el-collapse-item>
                  </el-collapse>
                  <!-- <div style="margin-top: 10px">{{ item.llmAnswer.answer }}</div> -->
                  <div v-if="item.llmAnswer.think" class="think" v-html="renderMarkdown(item.llmAnswer.think)"></div>
                  <div style="margin-top: 10px" v-html="renderMarkdown(item.llmAnswer.answer)"></div>
                </div>
              </div>
              <!-- sql -->
              <div class="answerData" v-if="item.sqlAnswer">
                <h2>数据库</h2>
                <div class="answer">
                  <el-table :data="item.sqlAnswer" style="width: 100%; height: 250px; overflow: scroll">
                    <el-table-column v-for="item in sqlAnswerKey" :prop="item" :key="item" :label="item" align="center"> </el-table-column>
                  </el-table>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="footer">
        <div class="footerContent">
          <textarea v-model="questionInput" :style="{ height: `${currentHeight}px`, overflowY: isScrollable ? 'auto' : 'hidden' }" placeholder="请输入您的问题..." @keydown="handleKeydown"> </textarea>
          <i class="el-icon-s-promotion icon" @click="sendQuestion"></i>
        </div>
      </div>
    </div>
    <div class="statistics">
      <div class="statisticsTitle">统计列表</div>
      <ul class="statisticsContent">
        <li v-for="item in statisticsData" :key="item.question" class="statisticsItem">
          <span style="padding-left: 30px"> {{ item.question }}</span>
        </li>
      </ul>
    </div>
    <el-dialog title="更多" :visible.sync="dialogVisible" width="1500px" :before-close="handleClose">
      <div class="dialogContent">
        <div class="contentLeft">
          <div>
            <div class="moreAnswer">{{ moreData.graphAnswer.answer }}</div>
            <graphECharts :graphAnswer="moreData.graphAnswer.graph"></graphECharts>
          </div>
        </div>
        <div class="contentRight">
          <div>
            <div class="source">
              <el-link :href="`${fileInfo.url}`" style="color: #209cc1" :underline="false" target="_blank">
                <el-tooltip class="item" effect="dark" content="点击可下载" placement="top">
                  <span> 来源：《{{ moreData.graphAnswer.fileName }}》</span>
                </el-tooltip>
              </el-link>
            </div>
            <div class="fileContent">
              <div v-if="fileInfo.fileSuffix == '.docx' || fileInfo.fileSuffix == '.doc'">
                <VueOfficeDocx ref="docxShow" style="width: 100%; height: calc(100vh - 400px)" :src="fileInfo.url" />
              </div>
              <div v-if="fileInfo.fileSuffix == '.pdf'">
                <!-- <VueOfficePdf style="width: 100%; height: calc(100vh - 400px)" :src="fileInfo.url" /> -->
                <pdfvuer :src="fileInfo.url" class="pdf" :page="moreData.graphAnswer.filePage"> </pdfvuer>
              </div>
              <el-pagination style="margin-top: 5px" v-if="fileInfo.fileSuffix == '.pdf'" background small layout="prev, pager, next" :current-page="moreData.graphAnswer.filePage" :page-count="fileTotalPage" @current-change="handleIndexChange">
              </el-pagination>
            </div>
          </div>
        </div>
      </div>

      <!-- <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
      </span> -->
    </el-dialog>
  </div>
</template>

<script>
import store from '@/store'
import { handlerAsk, getQAHistoryList, getGroup, getQAHistoryListAll, getQAHistoryDetail, removeQAHistory } from '@/api/als/intelligentQA'
import graphECharts from '@/views/als/components/Charts/graph.vue'
import { getListByIdsApi } from '@/api/als/oss'
//引入VueOfficeDocx组件
import VueOfficeDocx from '@vue-office/docx'
// import VueOfficePdf from '@vue-office/pdf'
import '@vue-office/docx/lib/index.css'
import pdfvuer from 'pdfvuer' // pdfvuer 版本为@1.6.1
import 'pdfjs-dist/build/pdf.worker.entry'
import * as marked from 'marked'

export default {
  name: 'IntelligentQA',
  // components: { graphECharts, VueOfficeDocx, VueOfficePdf },
  components: { graphECharts, VueOfficeDocx, pdfvuer },
  data() {
    return {
      dialogVisible: false,
      chatInfo: [],
      questionInput: '', //202310150010
      currentHeight: 'auto',
      lastScrollHeight: 0,
      isScrollable: false,
      moreData: {
        userId: '',
        graphAnswer: {},
        llmAnswer: {
          docs: '',
          answer: ''
        },
        sqlAnswer: []
      },
      historyData: [],
      fileInfo: {},
      statisticsData: [],
      // TODO 问答给数据后将该字段替换
      filePage: '2',
      fileTotalPage: null,
      sqlAnswerKey: [],
      collapseActiveNames: [],
      main: null,
      askUrl: '/als/algorithm/execute/qa'
    }
  },
  mounted() {
    this.main = document.getElementsByClassName('main')[0]
    this.getHistoryAll()
    this.getGroupAPI()
    this.adjustHeight()
  },
  watch: {
    chatInfo: {
      handler() {
        // 当原始列变化时，更新本地列
        setTimeout(() => {
          this.main.scrollTop = this.main.scrollHeight
        }, 0)
      },
      deep: true
    }
  },
  created() {},
  methods: {
    adjustHeight() {
      const textarea = this.$el.querySelector('textarea')
      this.currentHeight = 'auto'
      if (textarea.scrollHeight < this.lastScrollHeight) {
        this.lastScrollHeight = this.lastScrollHeight - 16
      }
      this.$nextTick(() => {
        this.currentHeight = Math.min(this.lastScrollHeight, 200)
        this.lastScrollHeight = textarea.scrollHeight
        if (textarea.scrollHeight >= 200) {
          this.isScrollable = true
        } else {
          this.isScrollable = false
        }
      })
    },

    async getHistoryAll() {
      try {
        const { data } = await getQAHistoryListAll()
        this.historyData = data.slice(0, 20)
        // console.log('this.historyData', this.historyData)
      } catch (error) {}
    },

    async getGroupAPI() {
      try {
        const { data } = await getGroup()
        this.statisticsData = data.slice(0, 10)
      } catch (error) {}
    },

    handleKeydown(event) {
      if (event.key === 'Enter') {
        if (event.ctrlKey) {
          // 按下了 Ctrl+Enter，则插入换行符
          this.questionInput += '\n'
          this.adjustHeight()
        } else {
          // 只是按下了 Enter，阻止默认行为并发送消息
          event.preventDefault()
          this.sendQuestion()
        }
      }
      if (event.key === 'Backspace') {
        if (this.questionInput) {
          this.adjustHeight()
        } else {
          this.currentHeight = '50'
        }
      }
    },

    async sendQuestion() {
      // 等处理过返回数据后，调用使视图保持在最底部
      // let main = this.$refs.main
      // let main = document.getElementsByClassName('main')
      const sendInput = {
        question: this.questionInput,
        userId: String(store.state.user.userInfo.user.userId)
      }
      try {
        if (this.questionInput.trim() === '') {
          return
        }
        this.chatInfo.push(sendInput, {
          answer: '正在解析您的问题，请稍后......'
        })
        this.questionInput = ''
        // const eventSource = new EventSource(this.askUrl)
        // eventSource.onmessage = (event) => {
        //   console.log('数据', event.data)
        // }
        const { code, data } = await handlerAsk(sendInput)
        if (code == 200) {
          const newData = this.handleData(JSON.parse(data))
          // const newData = this.handleData(data)
          this.chatInfo.pop()
          this.chatInfo.push(newData)

          // 获取sql回答的键
          this.sqlAnswerKey = Object.keys(JSON.parse(data).sqlAnswer[0])
        }
      } catch (error) {}
    },

    handleData(data) {
      if (data.graphAnswer?.graph) {
        const graphAnswer = eval('(' + data.graphAnswer.graph + ')')
        data.graphAnswer.graph = graphAnswer
        const categories = []
        data.graphAnswer.graph.data.forEach((node) => {
          const flag = categories.find((item) => {
            return item.name === node.category
          })
          if (!flag) {
            categories.push({ name: node.category })
          }
        })
        data.graphAnswer.graph.categories = categories
      }
      return data
    },

    handleMore(data) {
      this.getListById(data.graphAnswer.ossId)
      this.moreData = data
      this.dialogVisible = true
    },

    async getListById(id) {
      const { data } = await getListByIdsApi(id)
      // pdf测试：250091300051144704，word测试：250122767632355328
      // const { data } = await getListByIdsApi('254798739581349888')
      this.fileInfo = data[0]
      if (this.fileInfo?.fileSuffix == '.pdf') {
        pdfvuer
          .createLoadingTask(this.fileInfo?.url)
          .then((pdf) => {
            this.fileTotalPage = pdf.numPages
          })
          .catch((err) => {
            console.error('PDF 加载失败:', err)
          })
      } else if (this.fileInfo?.fileSuffix == '.docx' || this.fileInfo?.fileSuffix == '.doc') {
      }
    },

    handleClose() {
      this.dialogVisible = false
      this.fileInfo = {}
    },

    async historyDetail(id) {
      try {
        const { code, data } = await getQAHistoryDetail(id)
        if (code == 200) {
          const newData = this.handleData(JSON.parse(data.answer))
          this.chatInfo = []
          this.chatInfo.push({
            question: data.question,
            userId: data.userId
          })
          if (newData.sqlAnswer) {
            this.sqlAnswerKey = Object.keys(newData.sqlAnswer[0])
          }
          this.chatInfo.push(newData)
        }
      } catch (error) {}
    },

    deleteHistory(id) {
      this.$confirm('是否删除该条记录？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
        .then(() => {
          this.removeQAHistoryAPI(id)
        })
        .catch(() => {})
    },

    async removeQAHistoryAPI(params) {
      try {
        const { code } = await removeQAHistory(params)
        if (code === 200) {
          this.$message({
            type: 'success',
            message: '操作成功!'
          })
          this.getHistoryAll()
        }
      } catch (error) {}
    },

    // currentPage 改变时触发事件
    handleIndexChange(current) {
      this.moreData.graphAnswer.filePage = current
      // this.fetch()
    },

    renderMarkdown(item) {
      // console.log('marked(item)', marked(item))
      // return marked(item)
      const html = marked.parse(item)
      return html
    }

    // const { code, data } = {
    //   code: 200,
    //   msg: '',
    //   data: {
    //     userId: 'user',
    //     graphAnswer: {
    //       answer: '解决办法为：更换电池或遥控器',
    //       fileName: '排故手册',
    //       ossId: '227701077942149120', //pdf
    //       // ossID: '227692224508796928', //word
    //       filePage: 2,
    //       graph: {
    //         data: [
    //           { name: '202310150010', category: 'HMC' },
    //           { name: '电视', category: '成品' },
    //           { name: '电视遥控器失灵', category: '故障描述' },
    //           { name: '家用电器', category: '系统' },
    //           { name: '更换电池或遥控器', category: '维修策略' }
    //         ],
    //         links: [
    //           { source: '202310150010', target: '电视', value: '成品' },
    //           { source: '202310150010', target: '电视遥控器失灵', value: '故障描述' },
    //           { source: '202310150010', target: '家用电器', value: '系统' },
    //           { source: '202310150010', target: '更换电池或遥控器', value: '维修策略' }
    //         ]
    //       }
    //     },
    //     llmAnswer: {
    //       docs: [
    //         '出处 [1] [大模型方案.docx](http://10.67.81.103:7861/knowledge_base/download_doc?knowledge_base_name=lqbz&file_name=%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%A1%88.docx) \n\nERNIE的部分版本如ERNIE 3.0等提供了API服务，允许开发者通过调用接口的方式使用模型。\n腾讯混元 (Hunyuan)\n腾讯发布的多模态预训练模型，虽然以多模态为主，但也具备较强的文本生成能力。\n腾讯的混元模型通常不是完全开源的，但提供了API服务供开发者使用。\n华为盘古 (Pangu)\n华为研发的大规模预训练模型，专门针对中文场景进行了优化。在中文文本生成、问答等方面表现优秀。\n华为盘古模型本身没有开源，但华为提供了相关的API服务，使开发者能够使用这些模型的能力。\n定义模型规模\n确定模型的参数量大小，这将直接影响到模型的能力和计算资源的需求。\n超参数设定\n设定学习率、批次、损失函数等超参数。\n训练环境搭建\n安装必要软件\n安装Python、CUDA、Anaconda、深度学习框架（如Tensorflow或者Pytorch）等。\n配置GPU/CPU资源\n确保有足够的算力资源来支持模型的训练。\n模型训练\n初始化模型\n根据选定的架构初始化模型。\n数据加载\n编写代码来加载和处理训练数据。\n训练循环\n实现训练循环，包括前向传播、损失计算、反向传播和权重更新。\n保存检查点\n定期保存检查点、以便后续使用或恢复训练。\n模型评估于优化\n定义评估指标\n选择合适的评估指标，如准确率、BLEU得分等。\n验证与测试\n使用验证集和测试集来评估模型性能。\n模型微调\n根据评估结果进行模型调整或优化。\n模型部署\n模型导出\n将训练好的模型转换成可部署的格式\n部署环境\n在本地服务器上部署环境，可能需要使用轻量化推理框架（如Tensorflow Lite、ONNX Runtime等）。\n接口开发\n\n',
    //         '出处 [2] [大模型方案.docx](http://10.67.81.103:7861/knowledge_base/download_doc?knowledge_base_name=lqbz&file_name=%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%A1%88.docx) \n\n使用验证集和测试集来评估模型性能。\n模型微调\n根据评估结果进行模型调整或优化。\n模型部署\n模型导出\n将训练好的模型转换成可部署的格式\n部署环境\n在本地服务器上部署环境，可能需要使用轻量化推理框架（如Tensorflow Lite、ONNX Runtime等）。\n接口开发\n开发API接口，使模型可以接受输入并返回预测结果。\n特定任务的微调\n任务数据准备\n收集针对特定任务的数据集。\n微调训练\n使用新数据集对模型进行微调，使其更擅长完成特定任务。\n评估与调整\n再次评估模型性能，并根据需要进行调整。\n持续改进\n用户反馈\n收集用户反馈，根据反馈进行模型的持续改进。\n版本控制\n使用版本控制系统来管理模型的不同版本。\n\n',
    //         '出处 [3] [大模型方案.docx](http://10.67.81.103:7861/knowledge_base/download_doc?knowledge_base_name=lqbz&file_name=%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%A1%88.docx) \n\n分词\n将文本分割成更小的单元（token），如单词或子词。\n编码\n将分词后的文本转换为模型可以理解的数值表示。\n序列截断\n如果文本过长，可能需要将其截断为固定长度的序列。\n填充\n如果序列不够长，则需要用特殊标记填充至固定长度。\n模型架构设计\n选择模型类型\n目前在中文问答领域较好的大语言模型包括但不限于以下几个：\n通义千问 (Qwen)\n由阿里云开发，提供了公开的API接口供开发者调用，也可以通过官方网站直接与模型交互。支持多种任务，包括开放域问答、代码生成、文本生成等。\n文心一言 (ERNIE Bot)\n百度推出的大型语言模型，虽然模型本身没有完全开源，但提供了API服务，允许开发者通过调用接口来使用模型的功能。其针对中文语境进行了优化。在多项中文NLP任务上表现出色，包括问答和对话。\n悟道·天鹰 (M6)\n来自达摩院的大规模多模态预训练模型，虽然主要应用于多模态任务，但在语言理解和生成方面也有很好的表现。特别是在跨模态理解和生成方面有独特的优势。\n达摩院的M6系列模型没有完全开源，但其技术细节和部分应用可以通过论文和官方文档获取。\n百度文心系列 (ERNIE)\n包括多个版本，如 ERNIE 3.0 Titan 和 ERNIE 3.0 Zeus，这些模型在多项中文NLP任务上取得了非常好的成绩。\n具有强大的语言理解能力，适用于问答、摘要等多种任务。\nERNIE的部分版本如ERNIE 3.0等提供了API服务，允许开发者通过调用接口的方式使用模型。\n腾讯混元 (Hunyuan)\n腾讯发布的多模态预训练模型，虽然以多模态为主，但也具备较强的文本生成能力。\n腾讯的混元模型通常不是完全开源的，但提供了API服务供开发者使用。\n\n'
    //       ],
    //       answer: '根据已知信息，无法回答该问题。'
    //     },
    //     sqlAnswer: [
    //         {
    //           日期: '2016-05-02',
    //           name: '王小虎',
    //           地址: '上海市普陀区金沙江路 1518 弄',
    //           sex: '男'
    //         },
    //         {
    //           日期: '2016-05-02',
    //           name: '王小虎',
    //           地址: '上海市普陀区金沙江路 1518 弄',
    //           sex: '男'
    //         }
    //       ]
    //   }
    // }
  }
}
</script>

<style scoped lang="scss">
@import './index.scss';
</style>
