|
@@ -0,0 +1,490 @@
|
|
|
+<template>
|
|
|
+ <div class="chat bg-gray-900 comBg">
|
|
|
+ <div class="flex flex-col items-center justify-center h-screen px-4">
|
|
|
+ <img class="absolute h-full" :src="baseApi + info.picture" alt="">
|
|
|
+ <!-- onClick={() => navigate('/')} -->
|
|
|
+ <div class="top">
|
|
|
+ <button class="tm_button absolute text-xs bg-none py-1 px-3 rounded flex" @click="goBack">
|
|
|
+ <i class="el-icon-arrow-left"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chatContent">
|
|
|
+ <!-- <div class="leftImg">
|
|
|
+ <img :src="baseApi + info.picture" alt="">
|
|
|
+ <div class="aiInfo">
|
|
|
+ <span class="photo">
|
|
|
+ <img :src="baseApi + info.picture">
|
|
|
+ </span>
|
|
|
+ <div class="info">
|
|
|
+ <div class="name">{{ info.characterName }}</div>
|
|
|
+ <div class="tags">
|
|
|
+ <span class="tag" v-for="(item, index) in info.labelArr" :key="index">
|
|
|
+ {{ item }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="infoContent">{{ info.prologue }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div> -->
|
|
|
+ <div class="chat-box">
|
|
|
+ <div class="messages" ref="messages">
|
|
|
+ <template v-for="(item, index) in returnMessage">
|
|
|
+ <div v-if="item.role == 'assistant'" class="text-white mb-4 flex" :key="index">
|
|
|
+ <!-- ai返回的信息 -->
|
|
|
+ <div class="pt-2 photo">
|
|
|
+ <img
|
|
|
+ :src="baseApi + info.picture"
|
|
|
+ class="rounded-full w-12 h-12 object-cover"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="message fex-1 ml-4 mt-2 p-2 rounded-r-md rounded-bl-md text-xs" >
|
|
|
+ <div v-show="!item.content" class="loadingMessage" >
|
|
|
+ <div v-loading="!item.content" element-loading-background="rgba(0, 0, 0, 0.0)"></div>
|
|
|
+ </div>
|
|
|
+ <p v-show="item.content">{{ item.content }} </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 用户发送的信息 -->
|
|
|
+ <div v-if="item.role == 'user'" class="text-white mb-4 flex me" :key="index">
|
|
|
+ <div class="pt-2 photo">
|
|
|
+ <img
|
|
|
+ src="@/assets/images/default_avatar_user.png"
|
|
|
+ class="rounded-full w-12 h-12 object-cover"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="message2 fex-1 mr-4 mt-2 p-2 rounded-l-md rounded-br-md text-xs">
|
|
|
+ <p>{{ item.content }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="absolute left-2 right-2 bottom-4 ">
|
|
|
+ <!-- <div class="flex items-center text-gray-400 text-xs mb-4 ">
|
|
|
+ <span>@Leon S Kennedy - Resident Evil</span>
|
|
|
+ </div> -->
|
|
|
+ <div class="flex">
|
|
|
+ <el-button class="buttonBg1 mButton" @click="newStart" style="margin-right: 5px;" type="primary" round icon="el-icon-plus"></el-button>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ placeholder="输入消息..."
|
|
|
+ class="flex-1 py-2 px-4 rounded-l-lg text-sm text-white focus:outline-none"
|
|
|
+ style="background: #ffffff0f; border: 1px solid #5b5b5e;"
|
|
|
+ v-model="content"
|
|
|
+ @keydown="Enterkey"
|
|
|
+ />
|
|
|
+ <button @click="getStreamChatWithWeb" class="buttonBg1 text-white px-4 rounded-r-lg text-sm">
|
|
|
+ 发送
|
|
|
+ </button>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { streamChatWithWebApi } from "@/api/chat.js"
|
|
|
+import { detailApi } from "@/api/detail.js"
|
|
|
+export default {
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ info: {},
|
|
|
+ messageLoading: false,
|
|
|
+ returnMessage: [],
|
|
|
+ content: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mounted() {
|
|
|
+ if (this.$route.query.characterId) {
|
|
|
+ this.getDetail(this.$route.query.characterId)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ returnMessage: {
|
|
|
+ handler(newVal, lodVal) {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ let messages = this.$refs.messages
|
|
|
+ messages.scrollTop = messages.scrollHeight
|
|
|
+ })
|
|
|
+ },
|
|
|
+ deep: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ getDetail(id) {
|
|
|
+ detailApi(id).then(res => {
|
|
|
+ console.log(res, '角色详情');
|
|
|
+ this.info = res.data
|
|
|
+ let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
|
|
|
+ if (HistoryMessage) {
|
|
|
+ this.returnMessage = HistoryMessage
|
|
|
+ } else {
|
|
|
+ this.returnMessage.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: this.info.firstContent
|
|
|
+ })
|
|
|
+ localStorage.setItem(`[${123},${this.info.id}]`, JSON.stringify(this.returnMessage));
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ goBack() {
|
|
|
+ this.$router.back()
|
|
|
+ },
|
|
|
+ newStart() {
|
|
|
+ // 清空历史数据
|
|
|
+ localStorage.removeItem(`[${123},${this.info.id}]`);
|
|
|
+ this.returnMessage = []
|
|
|
+ this.getDetail(this.info.id)
|
|
|
+ },
|
|
|
+ Enterkey(e) {
|
|
|
+ if (e.keyCode == 13) {
|
|
|
+ this.getStreamChatWithWeb()
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async getStreamChatWithWeb_old() {
|
|
|
+ this.messageLoading = true
|
|
|
+ if (!this.content) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.returnMessage.push({
|
|
|
+ role: 'user',
|
|
|
+ content: this.content
|
|
|
+ })
|
|
|
+ let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
|
|
|
+ // 历史记录取最新的20条传给后端
|
|
|
+ if (HistoryMessage && HistoryMessage.length > 20) {
|
|
|
+ HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
|
|
|
+ }
|
|
|
+ let params = {
|
|
|
+ historyMessage: HistoryMessage || [],
|
|
|
+ characterId: this.info.id,
|
|
|
+ prompt: JSON.parse(JSON.stringify(this.content))
|
|
|
+ }
|
|
|
+ // 清空输入框的值
|
|
|
+ this.content = ''
|
|
|
+ // 新增一条ai信息
|
|
|
+ this.returnMessage.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: ''
|
|
|
+ })
|
|
|
+ let res = await streamChatWithWebApi(params)
|
|
|
+ console.log(res.data.message.content, 'res');
|
|
|
+ let content = res.data.message.content
|
|
|
+ let index = 0
|
|
|
+ let xing = 1
|
|
|
+ let messageInterval = setInterval(() => {
|
|
|
+ if (index > content.length - 1) {
|
|
|
+ console.log('结束');
|
|
|
+ // 消息全部显示后存到历史localStorage
|
|
|
+ let HistoryMessage = []
|
|
|
+ // 历史只存100条,长度超出截取最新的
|
|
|
+ if (this.returnMessage.length > 100) {
|
|
|
+ HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
|
|
|
+ } else {
|
|
|
+ HistoryMessage = JSON.stringify(this.returnMessage)
|
|
|
+ }
|
|
|
+ localStorage.setItem(`[${123},${this.info.id}]`, HistoryMessage);
|
|
|
+
|
|
|
+ clearInterval(messageInterval)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ console.log(content[index], 'content[index]');
|
|
|
+ if (content[index] == "*") {
|
|
|
+ if (xing == 1) {
|
|
|
+ xing = 2
|
|
|
+ this.returnMessage[this.returnMessage.length - 1].content += "("
|
|
|
+ } else if (xing == 2) {
|
|
|
+ xing = 1
|
|
|
+ this.returnMessage[this.returnMessage.length - 1].content += ")"
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.returnMessage[this.returnMessage.length - 1].content += content[index]
|
|
|
+ }
|
|
|
+ index += 1
|
|
|
+ }, 50)
|
|
|
+ },
|
|
|
+ async getStreamChatWithWeb() {
|
|
|
+ this.messageLoading = true
|
|
|
+ if (!this.content) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.returnMessage.push({
|
|
|
+ role: 'user',
|
|
|
+ content: this.content
|
|
|
+ })
|
|
|
+ let HistoryMessage = JSON.parse(localStorage.getItem(`[${123},${this.info.id}]`));
|
|
|
+ // 历史记录取最新的20条传给后端
|
|
|
+ if (HistoryMessage && HistoryMessage.length > 20) {
|
|
|
+ HistoryMessage = HistoryMessage.slice(HistoryMessage.length - 1, 20)
|
|
|
+ }
|
|
|
+ let params = {
|
|
|
+ HistoryMessage: HistoryMessage || [],
|
|
|
+ characterId: this.info.id,
|
|
|
+ prompt: this.content
|
|
|
+ }
|
|
|
+ // 新增一条ai信息
|
|
|
+ this.returnMessage.push({
|
|
|
+ role: 'assistant',
|
|
|
+ content: ''
|
|
|
+ })
|
|
|
+ // 清空输入框的值
|
|
|
+ this.content = ''
|
|
|
+
|
|
|
+ let res = await streamChatWithWebApi(params)
|
|
|
+ console.log(res, 'res');
|
|
|
+ this.messageLoading = false
|
|
|
+
|
|
|
+ if (res.status != 200) {
|
|
|
+ this.$message.error(res.statusText)
|
|
|
+ this.returnMessage.splice(this.returnMessage.length - 1, 1)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const reader = res.body.getReader()
|
|
|
+ const decoder=new TextDecoder()
|
|
|
+ while(1){
|
|
|
+ const {done, value} = await reader.read()
|
|
|
+ // console.log(done, 'done');
|
|
|
+ if(done){
|
|
|
+ console.log(value, 'value');
|
|
|
+ // if (typeof(value) == "undefined") {
|
|
|
+ // this.$message.error('错误')
|
|
|
+ // this.returnMessage.splice(this.returnMessage.length - 1, 1)
|
|
|
+ // }
|
|
|
+ console.log('结束');
|
|
|
+ // 消息全部显示后存到历史localStorage
|
|
|
+ let HistoryMessage = []
|
|
|
+ // 历史只存100条,长度超出截取最新的
|
|
|
+ if (this.returnMessage.length > 100) {
|
|
|
+ HistoryMessage = JSON.stringify(this.returnMessage.slice(this.returnMessage.length - 100, 100))
|
|
|
+ } else {
|
|
|
+ HistoryMessage = JSON.stringify(this.returnMessage)
|
|
|
+ }
|
|
|
+ localStorage.setItem(`[${123},${this.info.id}]`, HistoryMessage);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ //txt就是一个一个的字 然后添加到页面上就可以了
|
|
|
+ const txt = decoder.decode(value).split('data:')
|
|
|
+ // const txt = decoder.decode(value)
|
|
|
+ this.addMessage(txt)
|
|
|
+ // let data = JSON.parse(txt).message.content
|
|
|
+ // console.log(txt, 'txt');
|
|
|
+ // if (this.isJSON(txt) && JSON.parse(txt).code != 200) {
|
|
|
+ // let data = JSON.parse(txt)
|
|
|
+ // this.$message.error(data.msg)
|
|
|
+ // this.returnMessage.splice(this.returnMessage.length - 1, 1)
|
|
|
+ // break;
|
|
|
+ // }
|
|
|
+
|
|
|
+ }
|
|
|
+ },
|
|
|
+ addMessage(value) {
|
|
|
+ for (let i = 0; i < value.length; i++) {
|
|
|
+ const element = value[i];
|
|
|
+ if (this.isJSON(element)) {
|
|
|
+ let txt = JSON.parse(element).content
|
|
|
+ this.returnMessage[this.returnMessage.length - 1].content += txt
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ isJSON(str) {
|
|
|
+ if (typeof str == 'string') {
|
|
|
+ try {
|
|
|
+ JSON.parse(str);
|
|
|
+ return true;
|
|
|
+ } catch(e) {
|
|
|
+ // console.log(e);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+
|
|
|
+.chat {
|
|
|
+ min-height: 100vh;
|
|
|
+}
|
|
|
+.top {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 60px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.chatContent {
|
|
|
+ // margin-top: 30px;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ display: flex;
|
|
|
+ height: calc(100% - 60px);
|
|
|
+ justify-content: center;
|
|
|
+ width: 100vw;
|
|
|
+
|
|
|
+ >.leftImg {
|
|
|
+ border-radius: 16px 16px 16px 16px;
|
|
|
+ height: 95%;
|
|
|
+ position: relative;
|
|
|
+ min-width: 380px;
|
|
|
+
|
|
|
+ >img {
|
|
|
+ border: 6px solid;
|
|
|
+ border-image: linear-gradient(180deg, #b48733, #e8cf97, #b48733) 6 6;
|
|
|
+ height: 99%;
|
|
|
+ margin-right: 5px;
|
|
|
+ }
|
|
|
+ >.aiInfo {
|
|
|
+ background-color: #ffffff1a;
|
|
|
+ border-radius: 24px 24px 24px 24px;
|
|
|
+ bottom: -70px;
|
|
|
+ display: flex;
|
|
|
+ height: 193px;
|
|
|
+ justify-content: center;
|
|
|
+ left: 50%;
|
|
|
+ position: absolute;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ >.photo {
|
|
|
+ width: 64px;
|
|
|
+ height: 64px;
|
|
|
+ font-size: 18px;
|
|
|
+ position: absolute;
|
|
|
+ top: -20px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ color: #fff;
|
|
|
+ line-height: 1.5714285714285714;
|
|
|
+ list-style: none;
|
|
|
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
|
|
+ display: inline-flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ overflow: hidden;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-align: center;
|
|
|
+ vertical-align: middle;
|
|
|
+ background: rgba(0, 0, 0, 0.25);
|
|
|
+ border: 1px solid transparent;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ >.info {
|
|
|
+ padding-top: 50px;
|
|
|
+ width: 80%;
|
|
|
+ >.name {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 22px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 31px;
|
|
|
+ text-align: center;
|
|
|
+ text-transform: none;
|
|
|
+ }
|
|
|
+ >.tags {
|
|
|
+ padding-bottom: 8px;
|
|
|
+ text-align: center;
|
|
|
+ >.tag {
|
|
|
+ margin-right: 4px;
|
|
|
+ background: #ffffff1a;
|
|
|
+ border-radius: 4px 4px 4px 4px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ line-height: 17px;
|
|
|
+ border-color: transparent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ >.infoContent {
|
|
|
+ color: #fff;
|
|
|
+ font-size: 16px;
|
|
|
+ font-style: normal;
|
|
|
+ font-weight: 500;
|
|
|
+ height: 80px;
|
|
|
+ line-height: 22px;
|
|
|
+ overflow-y: auto;
|
|
|
+ text-align: center;
|
|
|
+ text-transform: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ >.chat-box {
|
|
|
+ position: relative;
|
|
|
+ background: linear-gradient(rgba(255, 255, 255, 0) 75%, rgb(34, 34, 34));
|
|
|
+ background-position: 50%;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: auto;
|
|
|
+ // border: 1px solid #635677 !important;
|
|
|
+ box-sizing: border-box;
|
|
|
+ color: #fff !important;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ // margin: 0 auto;
|
|
|
+ width: 1010px;
|
|
|
+ padding: 25px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.messages {
|
|
|
+ height: calc(100% - 68px );
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.messages::-webkit-scrollbar {
|
|
|
+ width: 0px;
|
|
|
+}
|
|
|
+.message {
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
+ // border: 1px solid #5b5b5e;
|
|
|
+ max-width: calc(100% - 72px);
|
|
|
+ // flex: 1;
|
|
|
+ min-width: 80px;
|
|
|
+}
|
|
|
+.message2 {
|
|
|
+ background: rgba(143, 37, 255, 0.6);
|
|
|
+ // border: 1px solid #5b5b5e;
|
|
|
+ max-width: calc(100% - 72px);
|
|
|
+ // flex: 1;
|
|
|
+ min-width: 80px;
|
|
|
+}
|
|
|
+.photo {
|
|
|
+ width: 56px;
|
|
|
+}
|
|
|
+.me {
|
|
|
+ flex-direction: row-reverse;
|
|
|
+}
|
|
|
+.loadingMessage {
|
|
|
+ position: relative;
|
|
|
+ // display: flex;
|
|
|
+ // justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+</style>
|
|
|
+<style scoped>
|
|
|
+
|
|
|
+ .loadingMessage >>> .el-loading-parent--relative {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+
|
|
|
+ }
|
|
|
+ .loadingMessage >>> .el-loading-spinner {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ .mButton {
|
|
|
+ border: solid 0px;
|
|
|
+ }
|
|
|
+ .tm_button {
|
|
|
+ border: 1px solid #FFF;
|
|
|
+ color: #FFF;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+</style>
|