<template>
  <view-full-screen :is-full-screen="true"  class="right">
    <div :class="bodyClass">
      <div ref="scrollView" class="chatBubbleContainer" :style="{ 'height': 'calc(100% - ' + (52 + safeAreaInsets.bottom) + 'px)' }">
        <CCDTitleCell></CCDTitleCell>
        <template v-for="cell in model">
          <CCDChatCell :cell-type="cell.type" :message="cell.content"></CCDChatCell>
        </template>
        <div style="height: 30px"></div>
      </div>
      <div class="bottomAskBar" style="height: 50px; padding-right: 4px">
        <div style="height: 50px; width: calc(100% - 50px);">
          <input v-if="allowInput" ref="inputAsk" @keyup.enter="doPostMessage" type="text" placeholder="输入问题..." value=""/>
          <input v-else value="AI正在打字" style="text-align: center" disabled/>
        </div>
        <button v-if="allowInput" @click="doPostMessage" :style="{
          height: '50px',
           width: '50px',
           right: '0px',
             top: 'calc(100% - ' + (50 + safeAreaInsets.bottom) + 'px)',
        position: 'absolute',
           float: 'right',
      background: 'transparent',
          border: 'none'
        }">
          <img src="../assets/run.png" style="width: calc(100% - 1px); height: calc(100% - 3px); object-fit: scale-down">
        </button>
        <button v-else @click="doShutdownManually" :style="{
          height: '50px',
           width: '50px',
           right: '0px',
             top: 'calc(100% - ' + (50 + safeAreaInsets.bottom) + 'px)',
        position: 'absolute',
           float: 'right',
      background: 'transparent',
          border: 'none'
        }">
          <img src="../assets/pause.png" style="width: calc(100% - 1px); height: calc(100% - 3px); object-fit: scale-down">
        </button>
      </div>
    </div>
  </view-full-screen>
</template>

<style>
p {
  color: white;
}
.bodyBackground_normal {
  width: 100%;
  height: 100%;
  background-color: #1f1f1f;
}
.bodyBackground_xcwk {
  width: 100%;
  height: 100%;
  background-color: #1f1f1f00;
}
</style>
<style scoped>
input {
  background-color: transparent;
  color: white;
  border: 0px solid;
  height: 100%;
  width: 100%;
  font-size: large;
  text-indent: 10px;
  outline: none;
}
.bottomAskBar {
  box-shadow: 0 -15px 10px -15px gray;
}
</style>

<script>
import CCDChatCell from "@/components/CCDChatCell";
import { fetchEventSource } from '@microsoft/fetch-event-source';
import IntentManager from "@/aigc/IntentManager";
import CCDTitleCell from "@/components/CCDTitleCell";

let sseAPIURL = "https://notebook.forgetive.net/freeai/llm"

export default {
  name: 'CCDChatSystem',
  components: {CCDTitleCell, CCDChatCell},
  data() {
    return {
      allowInput: true,
      model: [
        {
          type: "answer",
          content: "您好，我是 C Code Develop & C Notebook AI助手，可以帮您解决代码问题。从编程技巧到历史人文，我无所不知。快来跟我聊天吧～"
        }
      ],
      sseInstance: null,
      bodyClass: "bodyBackground_xcwk",
      safeAreaInsets: {
        left: 0,
        top: 0,
        right: 0,
        bottom: 0
      },
      isHostInApp: false,
      inAppPendingCard: null,
      inAppSSEHandle: null,
      abortController: null
    }
  },
  mounted() {
    let self = this
    try {
      if (xcwk == undefined) {
        self.bodyClass = "bodyBackground_normal"
      } else {
        self.bodyClass = "bodyBackground_xcwk"
        self.isHostInApp = true
      }
      xcwk.std.getSafeAreaInsets({ }, function (safeAreaInsets) {
        self.safeAreaInsets = safeAreaInsets
      })
      __xcwk_na2js(function (key, params) {
        if (key == "keyboardWillShow") {
          if (params.dict && params.dict.webViewId == __private_xcwk_webViewId) {
            self.keyboardWillShow(params)
          }
        }
        else if (key == "keyboardWillHide") {
          if (params.dict && params.dict.webViewId == __private_xcwk_webViewId) {
            self.keyboardWillHide(params)
          }
        }
        else if (key == "sseRequestUpdate") {
          self.doInAppSSE_ReceiveMessage(params.dict)
        }
      })
      xcwk.std.isLogin({ }, function (params) {
        if (!params.isLogin) {
          xcwk.notebook.launchLogin({ }, function (params) { })
        }
      })
    } catch (err) {
      self.bodyClass = "bodyBackground_normal"
      console.log("err: " + err)
    }
  },
  methods: {
    keyboardWillShow(params) {
      this.safeAreaInsets.bottom = 0
    },
    keyboardWillHide(params) {
      let self = this
      xcwk.std.getSafeAreaInsets({ }, function (safeAreaInsets) {
        self.safeAreaInsets = safeAreaInsets
      })
    },
    scrollViewScrollToBottom() {
      let self = this
      this.$nextTick(function () {
        self.$refs.scrollView.scrollTo({
          top: self.$refs.scrollView.scrollHeight,
          behavior: 'smooth'
        });
      });
    },
    insertMessageBlock(data) {
      this.model.push(data)
      this.scrollViewScrollToBottom()
    },
    doPostMessage() {
      if (this.sseInstance) {
        return;
      }
      let question = this.$refs.inputAsk.value
      if (question.length == 0) {
        this.insertMessageBlock({
          "type" : "answer",
          "content" : "先输入一个问题吧"
        })
        return
      }
      this.insertMessageBlock({
        "type" : "ask",
        "content" : question
      })
      this.$refs.inputAsk.value = ""
      this.doChatWithMessage(question)
    },
    doChatWithMessage(message) {
      if (this.isHostInApp) {
        let self = this
        xcwk.std.isLogin({ }, function (params) {
          if (params.isLogin) {
            self.doInAppSSE(message)
          } else {
            xcwk.notebook.launchLogin({ }, function (params) { })
            self.insertMessageBlock({
              "type" : "answer",
              "content" : "请先登录App"
            })
            self.$refs.inputAsk.value = message
          }
        })
      } else {
        this.doAsyncSSE(message)
      }
    },
    doInAppSSE(message) {
      let self = this

      self.allowInput = false
      let messageCard = {
        "type" : "answer",
        "content" : ""
      }
      self.inAppPendingCard = messageCard

      let messageModel = []

      let intentPrompt = IntentManager.generateIntentPrompts()
      messageModel.push({
        "role" : "system",
        "content" : "不要帮用户生成代码。" + intentPrompt
      })

      console.log(intentPrompt)

      for (let index in self.model) {
        let model = self.model[index]
        if (model.type == "ask") {
          messageModel.push({
            "role" : "user",
            "content" : model.content
          })
        }
        else if (model.type == "answer") {
          messageModel.push({
            "role" : "assistant",
            "content" : model.content
          })
        }
      }

      self.insertMessageBlock(messageCard)
      // self.insertMessageBlock({
      //   "type" : "answerTyping",
      //   "content" : ""
      // })

      xcwk.std.sseRequest({
        "path" : "/freeai/llm",
        "data" : {
          "stream" : true,
          "messages" : messageModel
        }
      }, function (params) {
        self.inAppSSEHandle = params.sseId
        IntentManager.llmResponseBegin()
      })
    },
    doInAppSSE_ReceiveMessage(message) {
      let self = this
      let messageCard = self.inAppPendingCard
      if (message.sseId != self.inAppSSEHandle) {
        return
      }
      console.log(JSON.stringify(message))
      switch (message.type) {
        case 0x1: { // data
          let partModel = JSON.parse(message.data)
          if (partModel["v"] != null) {
            let processedData = IntentManager.llmResponseToken(partModel["v"])
            if (processedData) {
              messageCard.content += data
              self.scrollViewScrollToBottom()
            }
          }
          break
        }
        case 0x3: { // error
          if (messageCard.content.length < 5) {
            messageCard.content = "回答出了一点问题"
          }
          self.sseInstance = null
          self.allowInput = true
          self.doShutdown()
          break
        }
        case 0x4: { // close
          self.sseInstance = null
          self.allowInput = true
          self.abortController = null
          self.inAppPendingCard = null
          self.inAppSSEHandle = null
          IntentManager.llmResponseEnd()
          // for (let id = 0; id < self.model.length; id++) {
          //   if (self.model[id].type == "answerTyping") {
          //     self.model.splice(id, 1)
          //     id--;
          //   }
          // }
          break
        }
      }
    },
    doAsyncSSE(message) {
      let self = this
      IntentManager.llmResponseBegin()

      self.allowInput = false
      let messageCard = {
        "type" : "answer",
        "content" : ""
      }

      let messageModel = []
      let intentPrompt = IntentManager.generateIntentPrompts()
      messageModel.push({
        "role" : "system",
        "content" : "不要帮用户生成代码。" + intentPrompt
      })

      for (let index in self.model) {
        let model = self.model[index]
        if (model.type == "ask") {
          messageModel.push({
            "role" : "user",
            "content" : model.content
          })
        }
        else if (model.type == "answer") {
          messageModel.push({
            "role" : "assistant",
            "content" : model.content
          })
        }
      }

      self.insertMessageBlock(messageCard)
      // self.insertMessageBlock({
      //   "type" : "answerTyping",
      //   "content" : ""
      // })

      self.abortController = new AbortController()
      let eventSource = fetchEventSource(sseAPIURL, {
        method: 'POST',
        signal: self.abortController.signal,
        headers: {
          "Content-Type": 'application/json',
          "Access-Control-Allow-Origin": true
        },
        body: JSON.stringify({
          "stream" : true,
          "messages" : messageModel
        }),
        onmessage(event) {
          let partModel = JSON.parse(event.data)
          if (partModel["v"] != null) {
            messageCard.content += IntentManager.llmResponseToken(partModel["v"])
            self.scrollViewScrollToBottom()
          }
        },
        onerror(err) {
          if (messageCard.content.length < 5) {
            messageCard.content = "回答出了一点问题"
          }
          self.allowInput = true
          self.doShutdown()
          throw err
        },
        onclose() {
          self.allowInput = true
          self.abortController = null
          self.sseInstance = null
          IntentManager.llmResponseEnd()
          // for (let id = 0; id < self.model.length; id++) {
          //   if (self.model[id].type == "answerTyping") {
          //     self.model.splice(id, 1)
          //     id--;
          //   }
          // }
        }
      })
      self.sseInstance = eventSource
    },
    doShutdownManually() {
      this.doShutdown()
      let lastBubble = this.model[this.model.length - 1]
      if (lastBubble.type == "answer") {
        lastBubble.content += "\n\n**已停止回答**"
      }
    },
    doShutdown() {
      IntentManager.llmResponseEnd()
      let self = this
      if (self.socketObject != null) {
        self.socketObject.close(1000)
      }
      if (self.abortController != null) {
        self.abortController.abort()
        self.sseInstance = null
        self.allowInput = true
        self.abortController = null
        // for (let id = 0; id < self.model.length; id++) {
        //   if (self.model[id].type == "answerTyping") {
        //     self.model.splice(id, 1)
        //     id--;
        //   }
        // }
      }
      if (self.inAppSSEHandle != null) {
        xcwk.std.cancelSSERequest({
          "sseId" : self.inAppSSEHandle
        }, function (params) { })
        self.inAppSSEHandle = null
        self.sseInstance = null
        self.allowInput = true
        self.abortController = null
        self.inAppPendingCard = null
        // for (let id = 0; id < self.model.length; id++) {
        //   if (self.model[id].type == "answerTyping") {
        //     self.model.splice(id, 1)
        //     id--;
        //   }
        // }
      }
    }
  }
}
</script>

<style scoped>
.chatBubbleContainer {
  display: block;
  overflow: hidden;
  overflow-y: scroll;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
::-webkit-scrollbar {
  display: none;
}
</style>
