chenrong 1 yıl önce
ebeveyn
işleme
2b1575e6c0
4 değiştirilmiş dosya ile 283 ekleme ve 45 silme
  1. 13 6
      src/api/chat.js
  2. 14 1
      src/utils/request.js
  3. 249 35
      src/views/chat/index.vue
  4. 7 3
      src/views/home.vue

+ 13 - 6
src/api/chat.js

@@ -1,14 +1,21 @@
-import request from '@/utils/request'
+// import request from '@/utils/request'
+
+// 设置终止信号
+// const controller = new AbortController();
+// setAbortController(controller)
+// const signal = controller.signal;
 
 // ai对话
 export function streamChatWithWebApi(data) {
-  return request({
-    url: `/system/chat/streamChatWithWeb`,
+  return fetch(`${process.env.VUE_APP_BASE_API}/system/chat/qwenChat`, {
     headers: {
-      isToken: false
+      isToken: false,
+      returnData: true,
+      'Content-Type': 'application/json'
     },
-    method: 'get',
-    params: data
+    method: 'POST',
+    body: JSON.stringify(data),
+    credentials: 'include',
   })
 }
 // http://localhost:8086/system/chat/streamChatWithWeb?content=牛肉怎么炒好吃

+ 14 - 1
src/utils/request.js

@@ -7,6 +7,7 @@ import { tansParams, blobValidate } from "@/utils/rcBase";
 import cache from "@/plugins/cache";
 import { saveAs } from "file-saver";
 
+let _returnData = false
 let downloadLoadingInstance;
 // 是否显示重新登录
 export let isRelogin = { show: false };
@@ -25,6 +26,12 @@ service.interceptors.request.use(
   (config) => {
     // 是否需要设置 token
     const isToken = (config.headers || {}).isToken === false;
+
+    const returnData = (config.headers || {}).returnData;
+    // console.log(config.headers, 'config.headers');
+    // console.log(returnData, 'returnData');
+    _returnData = returnData
+
     // 是否需要防止数据重复提交
     const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
     if (getToken() && !isToken) {
@@ -131,7 +138,13 @@ service.interceptors.response.use(
       });
       return Promise.reject("error");
     } else {
-      return res.data;
+      // console.log(_returnData, '_returnData');
+      if (_returnData) {
+        return res;
+      } else {
+        return res.data;
+      }
+      
     }
   },
   (error) => {

+ 249 - 35
src/views/chat/index.vue

@@ -10,43 +10,78 @@
         <!-- <i class="fas fa-chevron-left mr-2"></i> -->
         返回
       </div>
-      <div
-        class="chat-box rounded-lg p-4 w-1/2"
-        style="backdropFilter: blur(5px);background: rgba(0, 0, 0, 0.5); height: 550px;"
-      >
-        <div class="text-white mb-4 flex">
-          <div class="pt-2">
-            <img
-              src="@/assets/images/1.png"
-              class="rounded-full mr-2 w-14"
-            />
-          </div>
-          <div class="message fex-1 ml-4  mt-2 p-2 rounded-r-md rounded-bl-md text-xs">
-            <p class="mt-2 ">
-              这段时间一直有些自我怀疑,又是新的一天,对我们这个年纪的我来说,又是不容易的一天。他说他在自己的世界中,自己是公认的老好人,至于他在玩电脑,还在玩手机,我真心希望了。现实中,他的游戏世界只有自己。
-            </p>
-            <p class="mt-2 text-sm">
-              世界的温暖和他的温柔,......什么?什么?"他问道,脸上露出自嘲的笑容。他完全不认识我了。......,他是什么人?"他有点不愿意相信,他脸上露出的温暖感觉的笑容,我喜......那,他的手的温度。
-            </p>
+      <div class="content">
+        <div class="leftImg">
+          <img src="https://fallfor.ai/public/img/uploads/11/img_1715399022390_00024-654400262.jpg" alt="">
+          <div class="aiInfo">
+            <span class="photo">
+              <img src="https://fallfor.ai/public/img/uploads/11/img_1715399064396_1 (2).gif">
+            </span>
+            <div class="info">
+              <div class="name">Juniper</div>
+              <div class="tags">
+                <span class="tag">Female</span>
+                <span class="tag">Dominant</span>
+                <span class="tag">Submissive</span>
+              </div>
+              <div class="infoContent">继母会做任何事情让你接受她。</div>
+            </div>
           </div>
         </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 class="chat-box"> 
+          <div class="messages">
+            <template v-for="(item, index) in returnMessage">
+              <div v-if="item.user == 'ai'" class="text-white mb-4 flex" :key="index">
+                <!-- ai返回的信息 -->
+                <div class="pt-2 photo">
+                  <img
+                    src="@/assets/images/1.png"
+                    class="rounded-full w-14"
+                  />
+                </div>
+                <div class="message fex-1 ml-4 mt-2 p-2 rounded-r-md rounded-bl-md text-xs" >
+                  <div v-show="messageLoading" class="loadingMessage" >
+                    <div v-loading="messageLoading" element-loading-background="rgba(0, 0, 0, 0.0)"></div>
+                  </div>
+                  <p v-show="!messageLoading" >{{ item.text }}</p>
+                </div>
+              </div>
+              <!-- 用户发送的信息 -->
+              <div v-if="item.user == 'me'" class="text-white mb-4 flex me" :key="index">
+                <div class="pt-2 photo">
+                  <img
+                    src="@/assets/images/1.png"
+                    class="rounded-full w-14"
+                  />
+                </div>
+                <div class="message fex-1 mr-4  mt-2 p-2 rounded-l-md rounded-br-md text-xs">
+                  <p>{{ item.text }}</p>
+                </div>
+              </div>
+            </template>
           </div>
-          <div class="flex">
-            <input
-              type="text"
-              placeholder="输入消息..."
-              class="flex-1 py-2 px-4 rounded-l-lg text-sm text-white bg-gray-700 focus:outline-none"
-              v-model="content"
-            />
-            <button @click="getStreamChatWithWeb" class="buttonBg1 text-white px-4 rounded-r-lg text-sm">
-              发送
-            </button>
+          
+          <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">
+              <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>
@@ -56,6 +91,8 @@ import { streamChatWithWebApi } from "@/api/chat.js"
 export default {
   data() {
     return {
+      messageLoading: true,
+      returnMessage: [],
       content: ''
     }
   },
@@ -63,24 +100,50 @@ export default {
     goBack() {
       this.$router.back()
     },
+    Enterkey(e) {
+      if (e.keyCode == 13) {
+        this.getStreamChatWithWeb()
+      }
+    },
     async getStreamChatWithWeb() {
+      this.messageLoading = true
       if (!this.content) {
         return
       }
+      this.returnMessage.push({
+        user: 'me',
+        text: this.content
+      })
       let params = {
         content: this.content
       }
+      this.returnMessage.push({
+        user: 'ai',
+        text: ''
+      })
       let res = await streamChatWithWebApi(params)
-      console.log(res, 'res');
-      const reader = res.getReader()
+      this.messageLoading = false
+      // 新增一条ai信息
+      if (res.status != 200) {
+        this.$message.error(res.statusText)
+        this.returnMessage.splice(this.returnMessage.length - 1, 1)
+        return
+      }
+      // 清空输入框的值
+      this.content = ''
+
+      console.log(res, 'res.body');
+      const reader = res.body.getReader()
       const decoder=new TextDecoder()
       while(1){
-        const {done,value} = await reader.read()
+        const {done, value} = await reader.read()
         if(done){
           break;
         }
         const txt = decoder.decode(value)
         console.log(txt, 'txt'); 
+        
+        this.returnMessage[this.returnMessage.length - 1].text += txt
         //txt就是一个一个的字 然后添加到页面上就可以了
       }
     }
@@ -93,7 +156,158 @@ export default {
 .chat {
   min-height: 100vh;
 }
+.content {
+  margin-top: 30px;
+  background-size: 100% 100%;
+  display: flex;
+  height: 90vh;
+  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 {
+    margin-left: 5px;
+    position: relative;
+    background-color: #ffffff1a;
+    background-position: 50%;
+    background-repeat: no-repeat;
+    background-size: auto;
+    border: 1px solid #635677 !important;
+    border-radius: 20px;
+    box-sizing: border-box;
+    color: #fff !important;
+    display: flex;
+    flex-direction: column;
+    height: 95%;
+    // margin: 0 auto;
+    width: 1010px;
+    padding: 25px;
+  }
+}
+
+.messages {
+  height: calc(100% - 68px );
+  overflow-y: auto;
+}
+.messages::-webkit-scrollbar {
+  width: 0px;
+}
 .message {
-  background: #414141;
+  background: #ffffff0f;
+  border: 1px solid #5b5b5e;
+  max-width: calc(100% - 56px);
+  // 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;
+  }
 </style>

+ 7 - 3
src/views/home.vue

@@ -29,7 +29,7 @@
         </div>
         <div class="flex flex-wrap">
           <div
-            v-for="(item, index) in labelData.slice(0, 8)"
+            v-for="(item, index) in labelData.slice(0, labelLength)"
             :key="index"
             :class="[
               selectLabel.indexOf(item.id) != -1 ? 'buttonBg1' : 'labelButton',
@@ -40,12 +40,15 @@
             <i class="fas fa-fire text-yellow-500 mr-2"></i>
             {{ item.labelName }}({{item.num}})
           </div>
-          <div v-show="labelData.length > 8" class="labelButton px-3 py-1 mx-1 mb-1 rounded cursor-pointer">
+          <div v-show="labelData.length > 8 && labelLength == 8" @click="labelLength = labelData.length"  class="labelButton px-3 py-1 mx-1 mb-1 rounded cursor-pointer">
             ....
           </div>
-          <div v-show="labelData.length > 8" class="labelButton px-3 py-1 mx-1 mb-1 rounded cursor-pointer">
+          <div v-show="labelData.length > 8 && labelLength == 8" @click="labelLength = labelData.length" class="labelButton px-3 py-1 mx-1 mb-1 rounded cursor-pointer">
             显示更多
           </div>
+          <div v-show="labelData.length > 8 && labelLength > 8" @click="labelLength = 8" class="labelButton px-3 py-1 mx-1 mb-1 rounded cursor-pointer">
+            隐藏
+          </div>
         </div>
 
         <div class="grid grid-cols-5 gap-4 mt-4">
@@ -106,6 +109,7 @@ export default {
   },
   data() {
     return {
+      labelLength: 8,
       searchName: '',
       labelData: [],
       selectLabel: [],