¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="mobile-chat-wrapper" style="height: 91vh;"> |
| | | <div class="chat-history"> |
| | | <div class="chat-content" ref="chatContent"> |
| | | <div class="chat-wrapper" v-for="(item, index) in chatList" :key="index"> |
| | | <div class="chat-friend" v-if="item.uid !== '1001'"> |
| | | <div class="info-time"> |
| | | <img :src="item.headImg" alt="" /> |
| | | <span>{{ item.name }}</span> |
| | | <span>{{ item.time }}</span> |
| | | </div> |
| | | <div class="chat-text" v-if="item.chatType == 0"> |
| | | <template v-if="isSend && index === chatList.length - 1"> |
| | | <span class="flash_cursor"></span> |
| | | </template> |
| | | <template v-else> |
| | | <pre>{{ item.msg }}</pre> |
| | | </template> |
| | | </div> |
| | | <div class="chat-img" v-if="item.chatType == 1"> |
| | | <img :src="item.msg" alt="表æ
" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" /> |
| | | <el-image :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image> |
| | | </div> |
| | | <div class="chat-img" v-if="item.chatType == 2"> |
| | | <div class="word-file"> |
| | | <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="chat-me" v-else> |
| | | <div class="info-time"> |
| | | <span>{{ item.name }}</span> |
| | | <span>{{ item.time }}</span> |
| | | <img :src="item.headImg" alt="" /> |
| | | </div> |
| | | <div class="chat-text" v-if="item.chatType == 0"> |
| | | {{ item.msg }} |
| | | </div> |
| | | <div class="chat-img" v-if="item.chatType == 1"> |
| | | <img :src="item.msg" alt="表æ
" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" /> |
| | | <el-image style="max-width: 300px; border-radius: 10px" :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image> |
| | | </div> |
| | | <div class="chat-img" v-if="item.chatType == 2"> |
| | | <div class="word-file"> |
| | | <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="chat-input-wrapper"> |
| | | <div style="display: flex; align-items: center"> |
| | | <input v-model="inputMsg" @change="sendText" :disabled="loading" class="input-text" autofocus placeholder="ç»å°æºåéæ¶æ¯" /> |
| | | <img class="send-icon" src="@/assets/img/emoji/rocket.png" alt="" @click="sendText" /> |
| | | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, nextTick,onActivated } from 'vue' |
| | | import { useRoute } from 'vue-router' |
| | | import { animation } from '@/utils/util' |
| | | import chatGPTHeadImg from '@/assets/img/head_portrait1.png' |
| | | import headPortrait from '@/assets/img/head_portrait.jpg' |
| | | import FileCard from '@/components/FileCard.vue' |
| | | import { ElMessage } from "element-plus" |
| | | import {checking} from './ai-wd.js' |
| | | |
| | | // å®ä¹ååºå¼æ°æ® |
| | | const route = useRoute() |
| | | const chatContent = ref(null) |
| | | const ws = ref(null) |
| | | const chatList = ref([ |
| | | { |
| | | headImg: chatGPTHeadImg, |
| | | name: 'å°æº', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: ' å°æºä¸ºæ¨æå¡', |
| | | chatType: 0, |
| | | uid: '1002' |
| | | } |
| | | ]) |
| | | const inputMsg = ref('') |
| | | const isSend = ref(false) |
| | | const fileList = ref([]) |
| | | const isProcessing = ref(false) |
| | | const loading = ref(true) |
| | | const srcImgList = ref([]) |
| | | |
| | | // å é¤å¾ç |
| | | const deleteImg = (index) => { |
| | | if (index >= 0 && index < fileList.value.length) { |
| | | fileList.value.splice(index, 1) |
| | | } |
| | | } |
| | | |
| | | // WebSocketæ¶æ¯æ¥æ¶ |
| | | const websocketonmessage = (e) => { |
| | | const redata = JSON.parse(e.data) |
| | | //æ°æ®æ¥æ¶ |
| | | let chatGPT = { |
| | | headImg: headPortrait, |
| | | name: 'DeepSeek', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: redata[0].text, |
| | | chatType: 0, //ä¿¡æ¯ç±»åï¼0æåï¼1å¾ç |
| | | uid: '1002' //uid |
| | | } |
| | | sendMsg(chatGPT) |
| | | isSend.value = false |
| | | } |
| | | |
| | | // WebSocketåéæ¶æ¯ |
| | | const websocketsend = (Data) => { |
| | | console.log("å³å°åéæ¶æ¯", Data) |
| | | if (ws.value && ws.value.readyState === WebSocket.OPEN) { |
| | | console.log("åéæ¶æ¯", ws.value) |
| | | console.log("åéæ¶æ¯", Data) |
| | | let fileUrls = fileList.value.map(item => item.file.fileUrl) |
| | | //æ°æ®åé |
| | | ws.value.send(Data + ":" + fileUrls.join(",")) |
| | | fileList.value = [] |
| | | inputMsg.value = '' |
| | | } |
| | | } |
| | | |
| | | // åéææ¬æ¶æ¯ |
| | | const sendText = () => { |
| | | if (inputMsg.value) { |
| | | let chatMsg = { |
| | | headImg: headPortrait, |
| | | name: 'å§é¾', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: inputMsg.value, |
| | | chatType: 0, //ä¿¡æ¯ç±»åï¼0æåï¼1å¾ç |
| | | uid: '1001' //uid |
| | | } |
| | | chatList.value.push(chatMsg) |
| | | let chatGPT = { |
| | | headImg: headPortrait, |
| | | name: 'å°æº', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: "", |
| | | chatType: 0, //ä¿¡æ¯ç±»åï¼0æåï¼1å¾ç |
| | | uid: '1002' //uid |
| | | } |
| | | chatList.value.push(chatGPT) // å°æ¥æ¶å°çæ¶æ¯åå¨å° messages æ°ç» |
| | | simulateStreamingOutput(chatGPT, inputMsg.value) |
| | | inputMsg.value = '' |
| | | |
| | | } else { |
| | | ElMessage({ |
| | | message: 'æ¶æ¯ä¸è½ä¸ºç©ºå¦~', |
| | | type: 'warning' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // åéä¿¡æ¯ |
| | | const sendMsg = (msgList) => { |
| | | chatList.value.push(msgList) |
| | | scrollBottom() |
| | | } |
| | | |
| | | // è·åçªå£é«åº¦å¹¶æ»å¨è³æåºå± |
| | | const scrollBottom = () => { |
| | | nextTick(() => { |
| | | const scrollDom = chatContent.value |
| | | animation(scrollDom, scrollDom.scrollHeight - scrollDom.offsetHeight) |
| | | }) |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶æ§è¡ |
| | | onActivated(() => { |
| | | chatList.value = [] |
| | | chatList.value.push({ |
| | | headImg: chatGPTHeadImg, |
| | | name: 'å°æº', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: 'å°æºä¸ºæ¨æå¡', |
| | | chatType: 0, |
| | | uid: '1002' |
| | | }) |
| | | chatList.value.push({ |
| | | headImg: chatGPTHeadImg, |
| | | name: 'å§é¾', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: route.query.keyWord, |
| | | chatType: 0, |
| | | uid: '1001' |
| | | }) |
| | | // æ·»å ä¸ä¸ªç©ºçå夿¶æ¯å ä½ |
| | | const replyMsg = { |
| | | headImg: chatGPTHeadImg, |
| | | name: 'å°æº', |
| | | time: new Date().toLocaleTimeString(), |
| | | msg: '', |
| | | chatType: 0, |
| | | uid: '1002' |
| | | } |
| | | chatList.value.push(replyMsg) |
| | | scrollBottom() |
| | | |
| | | // å¦æææ¥è¯¢å
³é®åï¼åæ¨¡ææµå¼è¾åº |
| | | if (route.query.keyWord) { |
| | | simulateStreamingOutput(replyMsg, route.query.keyWord) |
| | | } |
| | | }) |
| | | // æ¨¡ææµå¼è¾åº |
| | | const simulateStreamingOutput = async (msgObj, keyWord) => { |
| | | loading.value = true |
| | | // çæ0.8-1.3ç§ä¹é´çéæºå»¶è¿ |
| | | const delay = Math.random() * 500 + 800 |
| | | |
| | | // 模æåå¤å
容ï¼å®é
åºç¨ä¸åºä»APIè·åï¼ |
| | | const responseText = `å
³äº"${keyWord}"çé®é¢ï¼ææ¥ä¸ºæ¨è§£çï¼\n` + checking(keyWord) |
| | | |
| | | isSend.value = true |
| | | |
| | | let index = 0 |
| | | setTimeout(() => { |
| | | const interval = setInterval(() => { |
| | | isSend.value = true |
| | | if (index < responseText.length) { |
| | | msgObj.msg += responseText.charAt(index) |
| | | index++ |
| | | isSend.value = false |
| | | scrollBottom() |
| | | } else { |
| | | clearInterval(interval) |
| | | isSend.value = false |
| | | loading.value = false |
| | | } |
| | | }, 50) // æ¯50msè¾åºä¸ä¸ªåç¬¦ï¼æ¨¡ææµå¼ææ |
| | | }, delay) |
| | | |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .mobile-chat-wrapper { |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | height: 91vh; |
| | | position: relative; |
| | | background-color: white; |
| | | |
| | | .chat-history { |
| | | flex: 1 1 0; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .chat-input-wrapper { |
| | | padding: 8px 16px 8px 8px; |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | .file-tt{ |
| | | flex-direction: column; |
| | | width: 200px; |
| | | display: flex; |
| | | padding: 5px; |
| | | border-radius: 5px; |
| | | margin-right: 5px; |
| | | background: #cacaca; |
| | | .file-item{ |
| | | width: 200px; |
| | | overflow:hidden; |
| | | word-wrap: break-word; |
| | | text-overflow:ellipsis; |
| | | display:-webkit-box; |
| | | -webkit-box-orient:vertical; |
| | | -webkit-line-clamp:2; |
| | | } |
| | | } |
| | | |
| | | .send-icon { |
| | | height: 40px; |
| | | margin-left: 16px; |
| | | } |
| | | .input-text{ |
| | | font-size: 18px; |
| | | width: 100%; |
| | | border-radius: 20px; |
| | | height: 80px; |
| | | padding-left: 10px; |
| | | //padding-top: 10px; |
| | | border: none; |
| | | color: black; /* ä¿®æ¹ææ¬é¢è²ä¸ºç½è² */ |
| | | background-color: #f5f4f4; /* ä¿®æ¹èæ¯é¢è²ä¸ºæ·±ç°è² */ |
| | | } |
| | | } |
| | | |
| | | .chat-content { |
| | | width: 100%; |
| | | height: 80%; |
| | | overflow-y: scroll; |
| | | padding: 20px; |
| | | box-sizing: border-box; |
| | | |
| | | &::-webkit-scrollbar { |
| | | width: 0; |
| | | /* Safari,Chrome éèæ»å¨æ¡ */ |
| | | height: 0; |
| | | /* Safari,Chrome éèæ»å¨æ¡ */ |
| | | display: none; |
| | | /* ç§»å¨ç«¯ãpad ä¸Safariï¼Chromeï¼éèæ»å¨æ¡ */ |
| | | } |
| | | |
| | | .chat-wrapper { |
| | | position: relative; |
| | | word-break: break-all; |
| | | |
| | | .chat-friend { |
| | | width: 100%; |
| | | float: left; |
| | | margin-bottom: 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: flex-start; |
| | | align-items: flex-start; |
| | | |
| | | .chat-text { |
| | | max-width: 90%; |
| | | padding: 20px; |
| | | border-radius: 20px 20px 20px 5px; |
| | | background-color: rgb(245, 248, 248); |
| | | color: black; |
| | | |
| | | &:hover { |
| | | background-color: rgb(232, 232, 232); |
| | | } |
| | | |
| | | pre { |
| | | white-space: break-spaces; |
| | | } |
| | | } |
| | | |
| | | .chat-img { |
| | | img { |
| | | width: 100px; |
| | | height: 100px; |
| | | } |
| | | } |
| | | |
| | | .info-time { |
| | | margin: 10px 0; |
| | | color: black; |
| | | font-size: 14px; |
| | | |
| | | img { |
| | | width: 30px; |
| | | height: 30px; |
| | | border-radius: 50%; |
| | | vertical-align: middle; |
| | | margin-right: 10px; |
| | | } |
| | | |
| | | span:last-child { |
| | | color: rgb(101, 104, 115); |
| | | margin-left: 10px; |
| | | vertical-align: middle; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .chat-me { |
| | | width: 100%; |
| | | float: right; |
| | | margin-bottom: 20px; |
| | | position: relative; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: flex-end; |
| | | align-items: flex-end; |
| | | |
| | | .chat-text { |
| | | float: right; |
| | | max-width: 90%; |
| | | padding: 20px; |
| | | border-radius: 20px 20px 5px 20px; |
| | | background-color: rgb(29, 144, 245); |
| | | color: #fff; |
| | | |
| | | &:hover { |
| | | background-color: rgb(26, 129, 219); |
| | | } |
| | | } |
| | | |
| | | .chat-img { |
| | | img { |
| | | max-width: 300px; |
| | | max-height: 200px; |
| | | border-radius: 10px; |
| | | } |
| | | } |
| | | |
| | | .info-time { |
| | | margin: 10px 0; |
| | | color: black; |
| | | font-size: 14px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | |
| | | img { |
| | | width: 30px; |
| | | height: 30px; |
| | | border-radius: 50%; |
| | | vertical-align: middle; |
| | | margin-left: 10px; |
| | | } |
| | | |
| | | span { |
| | | line-height: 30px; |
| | | } |
| | | |
| | | span:first-child { |
| | | color: rgb(101, 104, 115); |
| | | margin-right: 10px; |
| | | vertical-align: middle; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .flash_cursor { |
| | | width: 20px; |
| | | height: 30px; |
| | | display: inline-block; |
| | | background: #d6e3f5; |
| | | opacity: 1; |
| | | animation: glow 800ms ease-out infinite alternate; |
| | | } |
| | | @keyframes glow { |
| | | 0% { |
| | | opacity: 1; |
| | | } |
| | | |
| | | 25% { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | 50% { |
| | | opacity: 0; |
| | | } |
| | | |
| | | 75% { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | 100% { |
| | | opacity: 1; |
| | | } |
| | | } |
| | | } |
| | | </style> |