<template>
  <view class="wrapper" @touchmove="touchmove">
    <view class="tips color_fff size_12 align_c" :class="{ 'show':ajax.loading }" @tap="getHistoryMsg">{{ajax.loadText}}
    </view>
    <view class="placeholder"></view>
    <view class="box-1" id="list-box" ref="box">
      <view class="talk-list">
        <view v-for="(item,index) in talkList" :key="index" :id="`msg-${item.id}`">
          <view class="item flex_col" :class=" item.type == 1 ? 'push':'pull' ">
            <image :src="item.type==1?avatar:item.pic" mode="aspectFill" class="pic"></image>
            <view v-if="talkList.length-1==index" class="content multiline-text">
              <!-- <rich-text :nodes="item.content"></rich-text> -->
              <bing-math v-if="c_content!=''" :key="`math-${item.id}`" class="bing-math" :latex="c_content"></bing-math>
              <view v-else class="blinking-box">|</view>
            </view>
            <view v-else class="content multiline-text">
              <!-- <rich-text :nodes="item.content"></rich-text> -->
              <bing-math :key="`math-${item.id}`" class="bing-math" :latex="item.content"></bing-math>
            </view>
          </view>
        </view>
        <view id="bottom-box"></view>
      </view>
    </view>
    <uni-transition custom-class="box-2" mode-class="slide-left" :show="showStop">
      <view class="flex_col">
        <view class="flex_grow content downsocket" @click="closeSocketTask">中断连接</view>
      </view>
    </uni-transition>
    <uni-transition custom-class="box-2" mode-class="slide-right" :show="!showStop">
      <view class="flex_col">
        <view class="flex_grow">
          <input type="text" class="content" v-model="content" placeholder="请输入聊天内容" @focus="focus" @confirm="send"
            placeholder-style="color:#DDD;" :cursor-spacing="6">
        </view>
        <button class="send" @tap="send">发送</button>
      </view>
    </uni-transition>
    <!-- <view v-show="showplc" :style="{'min-height': (keyboardHeight+200)+'px'}" class="placeholder">显示</view> -->
  </view>
</template>

<script>
  import * as base64 from "base-64"
  import CryptoJS from '../../static/crypto-js/crypto-js.js'
  import parser from '../../static/fast-xml-parser/src/parser'
  import * as utf8 from "utf8"
  import BingMath from "@/components/bing-math/bing-math.vue"
  export default {
    components: {
      'bing-math': BingMath
    },
    data() {
      return {
        wssType: 'nongye', // 网络连接类型
        avatarType: "/static/icon/ny.png", // AI头像类型
        talkList: [],
        ajax: {
          rows: 15, //每页数量
          page: 1, //页码
          flag: false, // 请求开关
          loading: false, // 加载中
          loadText: '正在获取消息'
        },
        keyboardHeight: 0,
        showplc: true,
        content: '',
        c_content: '',
        n_content: '',
        avatar: '/static/avatar.png', // 用户头像
        params: [], // 发送的消息内容
        timer: '',
        socketTask: {},
        TEXT: '',
        historyTextList: [], // 历史会话信息，由于最大token12000,可以结合实际使用，进行移出
        tempRes: '', // 临时答复保存
        socketing: false, // 是否正在接收
        showStop: false, // 是否显示中断按钮
        scrollTop: 0,
        shouldScrollToBottom: true
      }
    },
    mounted() {
      this.$nextTick(() => {
        this.getHistoryMsg();
      });
      uni.onKeyboardHeightChange(e => {
        let h = this.keyboardHeight;
        this.keyboardHeight = e.height;
        this.$nextTick(() => {
          setTimeout(() => {
            uni.pageScrollTo({
              scrollTop: 9999999, // 当前位置向下滚动
              duration: 300 // 滚动过渡时间为300ms，默认值为300ms
            });
          }, 0)
        })
      })
    },
    onLoad() {
      let avatar = uni.getStorageSync('avatar');
      avatar ? this.avatar = avatar : null;
      uni.$on('MPinfo', (e) => {
        this.avatar = e.avatar;
      })
    },
    beforeDestroy() {
      // #ifdef APP-PLUS
      uni.offKeyboardHeightChange();
      // #endif
    },
    onPageScroll(e) {
      if (e.scrollTop < 5) {
        this.getHistoryMsg();
      }
    },
    watch: {
      n_content(n, o) {
        // this.c_content = n;
        if (this.timer) clearInterval(this.timer);
        let cl = this.c_content.length;
        let nc = this.n_content.split('');
        this.timer = setInterval(() => {
          if (cl < nc.length) {
            this.c_content += nc[cl];
            cl++;
            if (cl % 6 == 0) this.$nextTick(() => {
              uni.pageScrollTo({
                scrollTop: 999999,
              })
            })
          } else {
            // console.log(this.socketing==false, cl == nc.length);
            if (this.socketing == false) this.showStop = false;
            clearInterval(this.timer);
            this.$nextTick(() => {
              uni.pageScrollTo({
                scrollTop: 9999999,
              })
            })
          }
        }, 60)
      },
    },
    methods: {
      copyText(str) {
        uni.setClipboardData({
          data: str,
          success: function() {
            uni.showToast({
              icon: 'none',
              title: '复制成功'
            });
          }
        });
      },
      // 获取历史消息
      getHistoryMsg() {
        return;
        if (this.ajax.flag) {
          return; //
        }

        let get = async () => {
          this.hideLoadTips();
          this.ajax.flag = false;
          let data = await this.joinHistoryMsg();

          console.log('----- 模拟数据格式，供参考 -----');
          console.log(data); // 查看请求返回的数据结构 

          // 获取待滚动元素选择器，解决插入数据后，滚动条定位时使用
          let selector = '';

          if (this.ajax.page > 1) {
            // 非第一页，则取历史消息数据的第一条信息元素
            selector = `#msg-${this.talkList[0].id}`;
          } else {
            // 第一页，则取当前消息数据的最后一条信息元素
            selector = `#msg-${data[data.length-1].id}`;
          }

          // 将获取到的消息数据合并到消息数组中
          this.talkList = [...data, ...this.talkList];

          // 数据挂载后执行，不懂的请自行阅读 Vue.js 文档对 Vue.nextTick 函数说明。
          this.$nextTick(() => {
            // 设置当前滚动的位置
            this.setPageScrollTo(selector);

            this.hideLoadTips(true);

            if (data.length < this.ajax.rows) {
              // 当前消息数据条数小于请求要求条数时，则无更多消息，不再允许请求。
              // 可在此处编写无更多消息数据时的逻辑
            } else {
              this.ajax.page++;

              // 延迟 200ms ，以保证设置窗口滚动已完成
              setTimeout(() => {
                this.ajax.flag = true;
              }, 200)
            }

          })
        }
        get();
      },
      // 拼接历史记录消息
      joinHistoryMsg() {
        let join = () => {
          let arr = [];

          //通过当前页码及页数，模拟数据内容
          let startIndex = (this.ajax.page - 1) * this.ajax.rows;
          let endIndex = startIndex + this.ajax.rows;
          for (let i = startIndex; i < endIndex; i++) {
            arr.push({
              "id": i, // 消息的ID
              "content": `这是历史记录的第${i+1}条消息`, // 消息内容
              "type": Math.random() > 0.5 ? 1 : 0, // 此为消息类别，设 1 为发出去的消息，0 为收到对方的消息,
              "pic": "/static/avatar.png" // 头像
            })
          }

          /*
          	颠倒数组中元素的顺序。将最新的数据排在本次接口返回数据的最后面。
          	后端接口按 消息的时间降序查找出当前页的数据后，再将本页数据按消息时间降序排序返回。
          	这是数据的重点，因为页面滚动条和上拉加载历史的问题。
           */
          arr.reverse();

          return arr;
        }

        // 此处用到 ES6 的 Promise 知识，不懂的请自行学习。
        return new Promise((done, fail) => {
          // 无数据请求接口，由 setTimeout 模拟，正式项目替换为 ajax 即可。
          setTimeout(() => {
            let data = join();
            done(data);
          }, 1500);
        })
      },
      focus() {
        // this.$nextTick(()=>{
        //   setTimeout(()=>{
        //     uni.pageScrollTo({
        //       scrollTop: 9999999, // 当前位置向下滚动
        //       duration: 300 // 滚动过渡时间为300ms，默认值为300ms
        //     });
        //   }, 0)
        // })
      },
      // 设置页面滚动位置
      setPageScrollTo(selector) {
        let view = uni.createSelectorQuery().in(this).select(selector);
        view.boundingClientRect((res) => {
          uni.pageScrollTo({
            scrollTop: res.top - 30, // -30 为多显示出大半个消息的高度，示意上面还有信息。
            duration: 0
          });
        }).exec();
      },
      // 隐藏加载提示
      hideLoadTips(flag) {
        if (flag) {
          this.ajax.loadText = '消息获取成功';
          setTimeout(() => {
            this.ajax.loading = false;
          }, 300);
        } else {
          this.ajax.loading = true;
          this.ajax.loadText = '正在获取消息';
        }
      },
      // 关闭连接
      closeSocketTask() {
        try {
          clearInterval(this.timer);
          this.talkList[this.talkList.length - 1].content = this.c_content + '';
          // console.log(this.talkList[this.talkList.length - 1].content);
          // this.c_content = '';
          // this.n_content = '';
          this.socketTask.close({
            code: 500, // APP端存在BUG,正常关闭的code为1000,无法正常关闭,需要将code换为其他值
            complete: (res) => {
              this.showStop = false;
              console.log('主动断开', res);
              this.wsLiveFlag = false;
            }
          })
        } catch (e) {
          //TODO handle the exception
        }
      },
      // 发送信息
      send() {
        if (!this.content) {
          uni.showToast({
            title: '请输入有效的内容',
            icon: 'none'
          })
          return;
        }
        this.showStop = true;
        // 将当前发送信息 添加到消息列表。
        let data = {
          "id": new Date().getTime(),
          "content": this.content,
          "type": 1,
          "pic": this.avatar
        }
        console.log(data);
        this.TEXT = this.content;
        this.talkList.push(data);
        this.talkList.push({
          "id": new Date().getTime(),
          "content": '',
          "type": 2,
          "pic": this.avatarType
        });
        this.n_content = '';
        this.c_content = '';
        this.socketing = true;
        // return ;
        this.$nextTick(() => {
          // 清空内容框中的内容
          this.content = '';
          // uni.pageScrollTo({
          //   scrollTop: 999999, // 设置一个超大值，以保证滚动条滚动到底部
          //   duration: 0
          // });
        })
        this.sendToSpark();

      },
      async sendToSpark() {
        // let myUrl = await this.getWebSocketUrl();
        this.tempRes = "";
        let realThis = this;
        this.socketTask = uni.connectSocket({
          //url: encodeURI(encodeURI(myUrl).replace(/\+/g, '%2B')),
          url: `wss://chat.lihaink.cn/chat?type=${this.wssType}&timestamp=${Date.now()}`,
          method: 'GET',
          token: '',
          success: res => {
            console.log(res, "ws成功连接...")
            realThis.wsLiveFlag = true;
          }
        })
        realThis.socketTask.onError((res) => {
          console.log("连接发生错误，请检查appid是否填写", res)
        })
        realThis.socketTask.onOpen((res) => {
          this.historyTextList.push({
            "role": "user",
            "content": this.TEXT
          })
          // 第一帧..........................................
          console.log('连接成功...')
          // let params = {
          //   "header": {
          //     "app_id": this.APPID,
          //     "uid": "aef9f963-7"
          //   },
          //   "parameter": {
          //     "chat": {
          //       "domain": "generalv2",
          //       "temperature": 0.5,
          //       "max_tokens": 1024
          //     }
          //   },
          //   "payload": {
          //     "message": {
          //       "text": this.historyTextList
          //     }
          //   }
          // };
          if (this.historyTextList.length > 9) this.params = JSON.parse(JSON.stringify(this.historyTextList
            .splice(-9)));
          else this.params = JSON.parse(JSON.stringify(this.historyTextList));
          this.isSurpass();
          realThis.socketTask.send({ // 发送消息，，都用uni的官方版本
            data: JSON.stringify(this.params),
            success() {
              console.log('第一帧发送成功');
            }
          });
        });

        // 接受到消息时
        realThis.socketTask.onMessage((res) => {
          console.log('收到API返回的内容：', res.data);
          let obj = JSON.parse(res.data)
          // console.log("我打印的"+obj.payload);
          if (!realThis.wsLiveFlag) return;
          let dataArray = obj.payload.choices.text;
          for (let i = 0; i < dataArray.length; i++) {
            this.talkList[this.talkList.length - 1].content += dataArray[i].content;
            this.n_content = this.talkList[this.talkList.length - 1].content;
            realThis.tempRes = realThis.tempRes + dataArray[i].content
          }
          let temp = JSON.parse(res.data)
          // console.log("0726",temp.header.code)
          if (temp.header.code !== 0) {
            this.socketing = false;
            console.log(`${temp.header.code}:${temp.message}`);
            realThis.socketTask.close({
              success(res) {
                console.log('关闭成功', res)
                realThis.wsLiveFlag = false;
              },
              fail(err) {
                console.log('关闭失败', err)
              }
            })
          }
          if (temp.header.code === 0) {
            if (res.data && temp.header.status === 2) {
              this.socketing = false;
              this.historyTextList.push({
                "role": "assistant",
                "content": this.tempRes
              })
              setTimeout(() => {
                realThis.socketTask.close({
                  success(res) {
                    console.log('关闭成功', res)
                    realThis.wsLiveFlag = false;
                  },
                  fail(err) {
                    // console.log('关闭失败', err)
                  }
                })
              }, 1000)
            }
          }
        })
      },
      // 判断文字是否超过五千字
      isSurpass() {
        let sum = this.params.reduce((accumulator, currentValue) => {
          return accumulator + currentValue.content;
        }, '');
        if (sum.length > 5000) {
          this.params.shift();
          return this.isSurpass();
        } else {
          console.log(`本次发送${sum.length}字`);
          return sum;
        }
      },
      // 鉴权
      getWebSocketUrl() {
        return new Promise((resolve, reject) => {
          // https://spark-api.xf-yun.com/v1.1/chat  V1.5 domain general
          // https://spark-api.xf-yun.com/v2.1/chat  V2.0 domain generalv2
          var url = "wss://spark-api.xf-yun.com/v2.1/chat";
          var host = "spark-api.xf-yun.com";
          var apiKeyName = "api_key";
          var date = new Date().toGMTString();
          var algorithm = "hmac-sha256";
          var headers = "host date request-line";
          var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2.1/chat HTTP/1.1`;
          var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.APISecret);
          var signature = CryptoJS.enc.Base64.stringify(signatureSha);
          var authorizationOrigin =
            `${apiKeyName}="${this.APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
          var authorization = base64.encode(authorizationOrigin);
          url = `${url}?authorization=${authorization}&date=${encodeURI(date)}&host=${host}`;

          // console.log(url)
          resolve(url);
        });
      },
      // 滚动到头部
      bindScroll() {
        if (this.userId == 0) {
          this.getHistory();
          this.getproductInfo();
          this.getOrderInfo();
          this.getRefundDetail();
          this.getStoreDetail();
        } else {
          this.getMerHistory();
        }
      },
      // 当滑动页面时,收起键盘,与微信聊天效果保持一致
      touchmove(e) {
        uni.hideKeyboard()
      }
    }
  }
</script>

<style lang="scss">
  @import "../../lib/global.scss";

  page {
    background-color: #f5f5f5;
    font-size: 28rpx;
  }

  @keyframes blink {
    0% {
      opacity: 1;
    }

    50% {
      opacity: 0;
    }

    100% {
      opacity: 1;
    }
  }

  .blinking-box {
    background-color: #333;
    color: #333;
    animation: blink 1s infinite;
    width: 2px;
    overflow: hidden;
  }

  .wrapper {
    height: auto !important;
  }

  /* 加载数据提示 */
  .tips {
    position: fixed;
    left: 0;
    top: var(--window-top);
    width: 100%;
    z-index: 9;
    background-color: rgba(0, 0, 0, 0.15);
    height: 72rpx;
    line-height: 72rpx;
    transform: translateY(-80rpx);
    transition: transform 0.3s ease-in-out 0s;

    &.show {
      transform: translateY(0);
    }
  }

  .box-1 {
    width: 100%;
    height: auto;
    min-height: calc(100vh - 100rpx);
    padding-bottom: 100rpx;
    box-sizing: content-box;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;

    /* 兼容iPhoneX */
    margin-bottom: 0;
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
  }

  .multiline-text {
    white-space: pre-line;
    /* 或 white-space: pre-wrap; */
  }

  .box-2 {
    position: fixed;
    left: 0;
    width: 100%;
    bottom: 0;
    height: auto;
    z-index: 2;
    border-top: #e5e5e5 solid 1px;
    box-sizing: content-box;
    background-color: #f5f5f5;
    transform: translateY(0);
    /* 初始化 transform 属性 */
    transition: transform 0.3s ease;
    /* 添加过渡效果 */

    /* 兼容iPhoneX */
    padding-bottom: 0;
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);

    >view {
      padding: 0 20rpx;
      height: 100rpx;
    }

    .content {
      background-color: #fff;
      height: 64rpx;
      padding: 0 20rpx;
      border-radius: 6rpx;
      font-size: 28rpx;
    }

    .send {
      background-color: #2573fb;
      color: #fff;
      height: 64rpx;
      margin-left: 20rpx;
      border-radius: 6rpx;
      padding: 0;
      width: 120rpx;
      line-height: 62rpx;

      &:active {
        background-color: #1573fb;
      }
    }
  }

  .talk-list {
    padding-bottom: 20rpx;

    /* 消息项，基础类 */
    .item {
      padding: 20rpx 20rpx 0 20rpx;
      align-items: flex-start;
      align-content: flex-start;
      color: #333;

      .pic {
        width: 92rpx;
        height: 92rpx;
        border-radius: 50%;
        border: #fff solid 1px;
      }

      .content {
        padding: 20rpx;
        border-radius: 4px;
        max-width: 500rpx;
        word-break: break-all;
        line-height: 52rpx;
        position: relative;
      }

      /* 收到的消息 */
      &.pull {
        .content {
          min-width: 20rpx;
          min-height: 52rpx;
          margin-left: 32rpx;
          background-color: #fff;

          &::after {
            content: '';
            display: block;
            width: 0;
            height: 0;
            border-top: 16rpx solid transparent;
            border-bottom: 16rpx solid transparent;
            border-right: 20rpx solid #fff;
            position: absolute;
            top: 30rpx;
            left: -18rpx;
          }
        }
      }

      /* 发出的消息 */
      &.push {
        /* 主轴为水平方向，起点在右端。使不修改DOM结构，也能改变元素排列顺序 */
        flex-direction: row-reverse;

        .content {
          min-width: 20rpx;
          min-height: 52rpx;
          margin-right: 32rpx;
          background-color: #2573fb;
          color: #fff;

          &::after {
            content: '';
            display: block;
            width: 0;
            height: 0;
            border-top: 16rpx solid transparent;
            border-bottom: 16rpx solid transparent;
            border-left: 20rpx solid #2573fb;
            position: absolute;
            top: 30rpx;
            right: -18rpx;
          }
        }
      }
    }
  }

  .bing-math {
    margin: 0 !important;
    padding: 0 !important;
  }

  .placeholder {
    width: 100vw;
    background-color: #1573fb;
    // background-color: transparent;
    // transform: translateY(0); /* 初始化 transform 属性 */
    // transition: transform 0.3s ease; /* 添加过渡效果 */
  }

  .downsocket {
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #2573fb !important;
    color: #fff !important;
  }
</style>