123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- <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>
|