Browse Source

【功能】在XCWK浏览框架下使用SSE端能力进行SSE请求,提升性能

xcbosa mbp16 1 year ago
parent
commit
3d86579526
4 changed files with 205 additions and 12 deletions
  1. 3 3
      package-lock.json
  2. 0 1
      package.json
  3. 7 1
      src/App.vue
  4. 195 7
      src/components/CCDChatSystem.vue

+ 3 - 3
package-lock.json

@@ -8,7 +8,7 @@
       "name": "ccdchat",
       "version": "1.0.0",
       "dependencies": {
-        "@microsoft/fetch-event-source": "git+https://github.com/Xuan-Yu-San/fetch-event-source.git#2.0.1",
+        "@microsoft/fetch-event-source": "^2.0.1",
         "highlight.js": "^11.9.0",
         "jquery": "^3.6.4",
         "marked": "4.0.0",
@@ -72,8 +72,8 @@
     },
     "node_modules/@microsoft/fetch-event-source": {
       "version": "2.0.1",
-      "resolved": "git+ssh://git@github.com/Xuan-Yu-San/fetch-event-source.git#6edc0d645d821361f1b0eaf5c924decb6ea899e1",
-      "license": "MIT"
+      "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz",
+      "integrity": "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="
     },
     "node_modules/@types/q": {
       "version": "1.5.8",

+ 0 - 1
package.json

@@ -11,7 +11,6 @@
   },
   "dependencies": {
     "@microsoft/fetch-event-source": "^2.0.1",
-    "@rangermauve/fetch-event-source": "^1.0.3",
     "highlight.js": "^11.9.0",
     "jquery": "^3.6.4",
     "marked": "4.0.0",

+ 7 - 1
src/App.vue

@@ -12,11 +12,17 @@ export default {
 
 <style>
 #app {
-  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  font-family: PingFang SC, STHeitiSC-Light, Helvetica Neue, Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
   text-align: center;
   color: #2c3e50;
   margin-top: 60px;
 }
+p {
+  font-family: PingFang SC, STHeitiSC-Light, Helvetica Neue, Helvetica, Arial, sans-serif;
+}
+code {
+  font-family: Menlo, Monaco, PingFang SC, STHeitiSC-Light, Helvetica Neue, Helvetica, Arial, sans-serif;
+}
 </style>

+ 195 - 7
src/components/CCDChatSystem.vue

@@ -1,7 +1,7 @@
 <template>
   <view-full-screen :is-full-screen="true"  class="right">
-    <div style="width: 100%; height: 100%; background-color: #1f1f1f">
-      <div ref="scrollView" style="overflow-y: scroll; height: calc(100% - 52px)">
+    <div :class="bodyClass">
+      <div ref="scrollView" :style="{ 'overflow-y': 'scroll', 'height': 'calc(100% - ' + (52 + safeAreaInsets.bottom) + 'px)' }">
         <template v-for="cell in model">
           <CCDChatCell :cell-type="cell.type" :message="cell.content" @shutdown="doShutdown"></CCDChatCell>
         </template>
@@ -10,9 +10,18 @@
       <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="机器人正在输入..." style="text-align: center" disabled/>
+          <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% - 50px); position: absolute; float: right; background: transparent; border: none">
+        <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)">
         </button>
       </div>
@@ -24,6 +33,16 @@
 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 {
@@ -66,13 +85,65 @@ export default {
       socketObject: null,
       wannaKill: false,
       sseInstance: null,
-      abortController: null
+      abortController: null,
+      bodyClass: "bodyBackground_xcwk",
+      safeAreaInsets: {
+        left: 0,
+        top: 0,
+        right: 0,
+        bottom: 0
+      },
+      isHostInApp: false,
+      inAppPendingCard: null,
+      inAppSSEHandle: null
     }
   },
   mounted() {
-
+    let self = this
+    try {
+      if (__private_xcwk_webViewId == undefined) {
+        self.bodyClass = "bodyBackground_normal"
+      } else {
+        self.bodyClass = "bodyBackground_xcwk"
+        self.isHostInApp = true
+      }
+      __xcwk_js2na("xcwkstd/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_js2na("xcwkstd/isLogin", { }, function (params) {
+        if (!params.isLogin) {
+          __xcwk_js2na("notebook/launchLogin", { }, function (params) { });
+        }
+      });
+    } catch (err) {
+      self.bodyClass = "bodyBackground_normal"
+    }
   },
   methods: {
+    keyboardWillShow(params) {
+      this.safeAreaInsets.bottom = 0
+    },
+    keyboardWillHide(params) {
+      let self = this
+      __xcwk_js2na("xcwkstd/getSafeAreaInsets", { }, function (safeAreaInsets) {
+        self.safeAreaInsets = safeAreaInsets
+      })
+    },
     scrollViewScrollToBottom() {
       let self = this
       this.$nextTick(function () {
@@ -103,10 +174,111 @@ export default {
         "content" : question
       })
       this.$refs.inputAsk.value = ""
-      this.doAsyncSSE(question)
+      this.doChatWithMessage(question)
+    },
+    doChatWithMessage(message) {
+      if (this.isHostInApp) {
+        let self = this
+        __xcwk_js2na("xcwkstd/isLogin", {}, function (params) {
+          if (params.isLogin) {
+            self.doInAppSSE(message)
+          } else {
+            __xcwk_js2na("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 = []
+      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_js2na("xcwkstd/sseRequest", {
+        "path" : "/freeai/llm",
+        "data" : {
+          "stream" : true,
+          "messages" : messageModel
+        }
+      }, function (params) {
+        self.inAppSSEHandle = params.sseId
+      });
+    },
+    doInAppSSE_ReceiveMessage(message) {
+      let self = this
+      let messageCard = self.inAppPendingCard
+      console.log(JSON.stringify(message))
+      switch (message.type) {
+        case 0x1: { // data
+          let partModel = JSON.parse(message.data)
+          if (partModel["v"] != null) {
+            messageCard.content += partModel["v"]
+            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
+          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
+
       self.allowInput = false
       let messageCard = {
         "type" : "answer",
@@ -257,6 +429,22 @@ export default {
           }
         }
       }
+      if (self.inAppSSEHandle != null) {
+        __xcwk_js2na("xcwkstd/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--;
+          }
+        }
+      }
     }
   }
 }