赞
踩
websocket封装可以看上一篇文章
//pc端 <template> <div class="common-layout theme-white"> <el-container> <el-aside> <div class="title-box"> <span>AI Chat</span> </div> <div class="chat-list"> <!-- <div class="search-box"> <el-input v-model="chatName" class="w-50 m-2" size="small" placeholder="搜索会话" @keyup="searchChat"> <template #prefix> <el-icon class="el-input__icon"> <Search /> </el-icon> </template> </el-input> </div> --> <div class="content" :style="{ height: leftBoxHeight + 'px' }"> <el-row v-for="chat in chatList" :key="chat.chat_id"> <div :class="chat.chat_id === activeChat.chat_id ? 'chat-list-item active' : 'chat-list-item'" @click="changeChat(chat)"> <el-image :src="chat.icon" class="avatar" /> <span class="chat-title-input" v-if="chat.edit"> <el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)" placeholder="请输入会话标题" /> </span> <span v-else class="chat-title">{{ chat.title }}</span> <span class="btn btn-check" v-if="chat.edit || chat.removing"> <el-icon @click="confirm($event, chat)"> <Check /> </el-icon> <el-icon @click="cancel($event, chat)"> <Close /> </el-icon> </span> <span class="btn" v-else> <el-icon title="编辑" @click="editChatTitle($event, chat)"> <Edit /> </el-icon> <el-icon title="删除会话" @click="removeChat($event, chat)"> <Delete /> </el-icon> </span> </div> </el-row> </div> </div> <div class="tool-box"> <el-dropdown :hide-on-click="true" class="user-info" trigger="click"> <span class="el-dropdown-link"> <el-image src="/images/logo.png" /> <span class="username">{{ phoneNumber }}</span> <el-icon> <ArrowDown /> </el-icon> </span> <template #dropdown> <el-dropdown-menu style="width: 296px;"> <el-dropdown-item @click="showConfig"> <el-icon> <Tools /> </el-icon> <span>修改密码</span> </el-dropdown-item> <!-- <el-dropdown-item @click="clearAllChats"> <el-icon> <Delete /> </el-icon> <span>清除所有会话</span> </el-dropdown-item> --> <el-dropdown-item @click="logout"> <i class="iconfont icon-logout"></i> <span>注销</span> </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </div> </el-aside> <el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)" element-loading-text="正在生成..."> <div class="chat-head"> <div class="chat-config"> <span class="role-select-label">选择类型:</span> <el-select v-model="roleId" filterable placeholder="类型" class="role-select" @change="newChat"> <el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"> <div class="role-option"> <el-image :src="item.icon"></el-image> <span>{{ item.name }}</span> </div> </el-option> </el-select> <!-- <el-select v-model="modelID" placeholder="模型" @change="newChat"> <el-option v-for="item in models" :key="item.id" :label="item.name" :value="item.id" /> </el-select> --> <el-button type="primary" @click="newChat"> <el-icon> <Plus /> </el-icon> 新建对话 </el-button> <!-- <el-button type="success" @click="exportChat" plain> <i class="iconfont icon-export"></i> <span>导出会话</span> </el-button> --> <!-- <el-button type="warning" @click="showFeedbackDialog = true"> <el-icon> <Promotion /> </el-icon> <span>意见反馈</span> </el-button> --> </div> </div> <div class="right-box" :style="{ height: mainWinHeight + 'px' }"> <div> <div id="container"> <div class="chat-box" id="chat-box" :style="{ height: chatBoxHeight + 'px' }"> <!-- <div v-if="showHello"> --> <!-- <welcome @send="autofillPrompt" /> --> <!-- </div> --> <div v-for="item in chatData" :key="item.id"> <chat-prompt v-if="item.type === 'prompt'" :icon="item.icon" :created-at="dateFormat(item['created_at'])" :tokens="item['tokens']" :model="getModelValue(modelID)" :content="item.content" /> <chat-reply v-else-if="item.type === 'reply'" :icon="item.icon" :org-content="item.orgContent" :created-at="dateFormat(item['created_at'])" :tokens="item['tokens']" :content="item.content" /> <chat-mid-journey v-else-if="item.type === 'mj'" :content="item.content" :role-id="item.role_id" :chat-id="item.chat_id" :icon="item.icon" @disable-input="disableInput(true)" @enable-input="enableInput" :created-at="dateFormat(item['created_at'])" /> </div> </div> <div class="re-generate"> <div class="btn-box"> <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain> <el-icon> <VideoPause /> </el-icon> 停止生成 </el-button> <el-button type="primary" v-if="showReGenerate" @click="reGenerate" plain> <el-icon> <RefreshRight /> </el-icon> 重新生成 </el-button> </div> </div> <div class="input-box"> <div class="input-container"> <el-input ref="textInput" v-model="prompt" v-on:keydown="inputKeyDown" autofocus type="textarea" :rows="2" placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行" /> <!-- <span class="send-btn" style="right: 50px;"> <el-button @click="sendImage"> <el-icon> <PictureFilled /> </el-icon> </el-button> </span> --> <span class="send-btn"> <el-button @click="sendMessage"> <el-icon> <Promotion /> </el-icon> </el-button> </span> </div> </div><!-- end input box --> </div><!-- end container --> </div><!-- end loading --> </div> </el-main> </el-container> <!--账户信息 --> <PasswordDialog :show="showConfigDialog" @hide="showConfigDialog = false" /> <!-- 上传图片 --> <el-dialog class="config-dialog" v-model="showimgUpload" :close-on-click-modal="true" :before-close="close" style="width:60%" title="上传图片"> <el-upload class="avatar-uploader" :auto-upload="true" :before-upload="beforeUpload" :show-file-list="false" :http-request="afterRead" accept="image/png, image/jpeg,image/jpg"> <img v-if="imageUrl" :src="imageUrl" class="avatar" /> <el-icon v-else class="avatar-uploader-icon"> <Plus /> </el-icon> </el-upload> <template #footer> <span class="dialog-footer"> <el-button type="primary" @click="handleConfirm"> 确定 </el-button> </span> </template> </el-dialog> </div> </template> <script setup> import { nextTick, onMounted, ref } from 'vue' import ChatPrompt from "@/components/ChatPrompt.vue"; import ChatReply from "@/components/ChatReply.vue"; import { ArrowDown, Check, Close, Delete, Edit, Plus, Promotion, RefreshRight, Search, Tools, VideoPause, PictureFilled } from '@element-plus/icons-vue' import 'highlight.js/styles/a11y-dark.css' import { dateFormat, isMobile, randString, removeArrayItem, UUID } from "@/utils/libs"; import { ElMessage, ElMessageBox } from "element-plus"; import hl from "highlight.js"; import { getSessionId, getUserToken, removeUserToken } from "@/store/session"; import { httpGet, httpPost } from "@/utils/http"; import { useRouter } from "vue-router"; import Clipboard from "clipboard"; import PasswordDialog from "@/components/PasswordDialog.vue"; import imgUpload from "@/components/imgUpload.vue"; import { checkSession } from "@/action/session"; import Welcome from "@/components/Welcome.vue"; import ChatMidJourney from "@/components/ChatMidJourney.vue"; import { createWebscoket, onSend, closeWs, start, onMessage } from '@/utils/socket.js' import { chineseChar2englishChar } from "@/utils/validate"; import Compressor from "compressorjs"; const title = ref('ChatGPT-智能助手'); const models = ref([]) const modelID = ref(0) const chatData = ref([]); const allChats = ref([]); // 会话列表 const chatList = ref(allChats.value); const activeChat = ref({}); const mainWinHeight = ref(0); // 主窗口高度 const chatBoxHeight = ref(0); // 聊天内容框高度 const leftBoxHeight = ref(0); const loading = ref(false); const loginUser = ref(null); const roles = ref([{ "id": 1, "created_at": 0, "updated_at": 0, "key": "gpt", "name": "文生图", "context": null, "hello_msg": "您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。", "icon": "/images/avatar/gpt.png", "enable": true, "sort": 1 }]); const roleId = ref(1) const newChatItem = ref(null); const router = useRouter(); const showConfigDialog = ref(false); const showimgUpload = ref(false); const isLogin = ref(false) const showHello = ref(true) const textInput = ref(null) const showFeedbackDialog = ref(false) const showDemoNotice = ref(false) const showNoticeKey = ref("SHOW_DEMO_NOTICE_") const socketMsg = ref(null) const imageUrl = ref(null) const token = ref("") const openId = ref("") const phoneNumber = ref('') const password = ref('') if (isMobile()) { router.replace("/mobile") } onMounted(() => { resizeElement(); token.value = localStorage.getItem('token') openId.value = localStorage.getItem('openId') phoneNumber.value = localStorage.getItem('phoneNumber') password.value = localStorage.getItem('password') models.value = { phoneNumber: phoneNumber.value, password: password.value } const clipboard = new Clipboard('.copy-reply, .copy-code-btn'); clipboard.on('success', () => { ElMessage.success('复制成功!'); }) clipboard.on('error', () => { ElMessage.error('复制失败!'); }) if (token.value) { let host = process.env.VUE_APP_WS_HOST createWebscoket(host + token.value, messagesCallBack) chatData.value = []; // 初始化聊天数据 previousText.value = '';//上一次提问 enableInput() activelyClose.value = false;//主动关闭 chatData.value.push({ content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', type: "reply", id: randString(32), icon: '/images/logo.png', orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', }) window.onresize = () => resizeElement(); } else { router.push('/login') } }); const getRoleById = function (rid) { for (let i = 0; i < roles.value.length; i++) { if (roles.value[i]['id'] === rid) { return roles.value[i]; } } return null; } //新消息监听 const messagesCallBack = (msg) => { let socketMsg = msg.msg.replace(/\ufeff/g, ""); let socketMsgTwo = JSON.parse(socketMsg) if (socketMsgTwo.msgType != -1 || socketMsgTwo.msgType == 200) { loading.value = true if (socketMsgTwo.contentUrl) { chatData.value.push({ type: "mj", key: randString(32), icon: '/images/logo.png', content: socketMsgTwo.contentUrl }) loading.value = false } // 将聊天框的滚动条滑动到最底部 nextTick(() => { document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight) }) enableInput() } } //发送socket消息到服务器 const sendMessagext = (data) => { onSend(JSON.stringify(data)) } //断开socket const closeWsTxt = () => { closeWs() } const prompt = ref(''); const showStopGenerate = ref(false); // 停止生成 const showReGenerate = ref(false); // 重新生成 const previousText = ref(''); // 上一次提问 const lineBuffer = ref(''); // 输出缓冲行 const activelyClose = ref(false); // 主动关闭 const canSend = ref(true); // 发送消息 const sendMessage = function () { console.log(prompt.value) start() if (canSend.value === false) { ElMessage.warning("AI 正在作答中,请稍后..."); return } if (prompt.value.trim().length === 0 || canSend.value === false) { return false; }else{ loading.value = true } // 追加消息 chatData.value.push({ content: md.render(prompt.value), type: "prompt", id: randString(32), icon: '', created_at: new Date().getTime(), }); nextTick(() => { document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight) }) showHello.value = false disableInput(false) // socket.value.send(prompt.value); var data = { userId: openId.value, toUserId: openId.value, content: chineseChar2englishChar(prompt.value), msgType: 2 } //websocket 发送 sendMessagext(data) previousText.value = prompt.value;//上一次tiwen prompt.value = ''; return true; } //发送图片 const sendImage = function () { showimgUpload.value = true } //上传前 const beforeUpload = (file) => { if (file.type.indexOf("image") < 0) { ElMessage.error('请上传正确的图片格式') return false; } }; //上传后 const afterRead = (file) => { // 压缩图片并上传 new Compressor(file.file, { quality: 0.6, success(result) { const formData = new FormData(); formData.append('file', result, result.name); // 执行上传操作 httpPost('/api/upload', formData).then((res) => { user.value.avatar = res.data ElMessage.success({ message: "上传成功", duration: 500 }) }).catch((e) => { ElMessage.error('图片上传失败:' + e.message) }) }, error(err) { console.log(err.message); }, }); }; //图片确定 const handleConfirm = () => { showimgUpload.value = false } const resizeElement = function () { chatBoxHeight.value = window.innerHeight - 51 - 82 - 38; mainWinHeight.value = window.innerHeight - 51; leftBoxHeight.value = window.innerHeight - 43 - 47 - 45; }; // 新建会话 const newChat = function () { // 获取当前聊天角色图标 let icon = ''; roles.value.forEach(item => { if (item['id'] === roleId.value) { icon = item['icon'] } }) newChatItem.value = { chat_id: "", icon: icon, role_id: roleId.value, model_id: modelID.value, title: '', edit: false, removing: false, }; activeChat.value = {} //取消激活的会话高亮 showStopGenerate.value = false; showReGenerate.value = false; connect(null, roleId.value) } const titleKeydown = (e, chat) => { if (e.keyCode === 13) { e.stopPropagation(); confirm(e, chat) } } const md = require('markdown-it')({ breaks: true, highlight: function (str, lang) { const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000) // 显示复制代码按钮 const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span> <textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>` if (lang && hl.getLanguage(lang)) { const langHtml = `<span class="lang-name">${lang}</span>` // 处理代码高亮 const preCode = hl.highlight(lang, str, true).value // 将代码包裹在 pre 中 return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>` } // 处理代码高亮 const preCode = md.utils.escapeHtml(str) // 将代码包裹在 pre 中 return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>` } }); // 取消修改 const cancel = function (event, chat) { event.stopPropagation(); chat.edit = false; chat.removing = false; } const disableInput = (force) => { canSend.value = false; showReGenerate.value = false; showStopGenerate.value = !force; } const enableInput = () => { canSend.value = true; showReGenerate.value = previousText.value !== ""; showStopGenerate.value = false; } // 自动填充 prompt const autofillPrompt = (text) => { prompt.value = text textInput.value.focus() // sendMessage() } // 登录输入框输入事件处理 const inputKeyDown = function (e) { if (e.keyCode === 13) { if (e.ctrlKey) { // Ctrl + Enter 换行 prompt.value += "\n"; return; } e.preventDefault(); sendMessage(); } } //账户信息 const showConfig = function () { showConfigDialog.value = true; } const logout = function () { activelyClose.value = true; localStorage.clear(); router.push('/login'); ElMessage.success('退出成功'); } const stopGenerate = function () { showStopGenerate.value = false; httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => { enableInput() }) } // 重新生成 const reGenerate = function () { disableInput(false) const text = '重新生成上述问题的答案:' + previousText.value; // 追加消息 loading.value=true chatData.value.push({ type: "prompt", id: randString(32), icon: '/images/avatar/mid_journey.png', content: md.render(text) }); var data = { userId: openId.value, toUserId: openId.value, content: chineseChar2englishChar(prompt.value), msgType: 2 } sendMessagext(data) } </script> <style scoped lang="stylus"> @import "@/assets/css/chat-plus.styl" </style> <style> .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; } .avatar-uploader .el-upload:hover { border-color: #409EFF; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; line-height: 178px; text-align: center; } .avatar { width: 178px; height: 178px; display: block; }</style>
//移动端 <template> <div class="app-background"> <van-config-provider theme="dark"> <div class="mobile-chat"> <van-sticky ref="navBarRef" :offset-top="0" position="top"> <!-- <van-nav-bar left-arrow left-text="返回" @click-left="router.back()"> --> <!-- <template #title> <van-dropdown-menu> <van-dropdown-item :title="title"> <van-cell center title="角色"> {{ role.name }}</van-cell> <van-cell center title="模型">{{ modelValue }}</van-cell> </van-dropdown-item> </van-dropdown-menu> </template> --> <!-- <template #right> <van-icon name="share-o" @click="showShare = true"/> </template> --> <!-- </van-nav-bar> --> </van-sticky> <!-- <van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="shareOptions" @select="shareChat" /> --> <div class="chat-list-wrapper"> <div id="message-list-box" :style="{ height: winHeight + 'px' }" class="message-list-box"> <van-list v-model:error="error" :finished="finished" error-text="请求失败,点击重新加载" @load="onLoad"> <van-cell v-for="item in chatData" :key="item" :border="false" class="message-line"> <chat-prompt v-if="item.type === 'prompt'" :content="item.content" :created-at="dateFormat(item['created_at'])" :icon="item.icon" :model="model" :tokens="item['tokens']" /> <chat-reply v-else-if="item.type === 'reply'" :content="item.content" :created-at="dateFormat(item['created_at'])" :icon="item.icon" :org-content="item.orgContent" :tokens="item['tokens']" /> <chat-mid-journey v-else-if="item.type === 'mj'" :content="item.content" :icon="item.icon" :role-id="role" :chat-id="chatId" @disable-input="disableInput(true)" @enable-input="enableInput" :created-at="dateFormat(item['created_at'])" /> </van-cell> </van-list> </div> </div> <div class="chat-box-wrapper"> <van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom"> <van-cell-group inset> <van-field v-model="prompt" center clearable placeholder="输入你的问题"> <template #button> <van-button size="small" type="primary" @click="sendMessage">发送</van-button> </template> <template #extra> <div class="icon-box" style="margin-left: 10rpx;"> <van-icon v-if="showStopGenerate" name="stop-circle-o" @click="stopGenerate" /> <van-icon v-if="showReGenerate" name="play-circle-o" @click="reGenerate" /> </div> </template> </van-field> </van-cell-group> </van-sticky> </div> </div> </van-config-provider> <van-overlay z-index="100" :show="loading" class="wrapper"> <van-loading vertical class="block"> <template #icon> <van-icon name="star-o" size="30" /> </template> 正在生成... </van-loading> </van-overlay> </div> </template> <script setup> import { nextTick, onMounted, ref } from "vue"; import { showToast } from "vant"; import { useRouter } from "vue-router"; import { dateFormat, randString, renderInputText, UUID } from "@/utils/libs"; import { getChatConfig } from "@/store/chat"; import { httpGet } from "@/utils/http"; import hl from "highlight.js"; import 'highlight.js/styles/a11y-dark.css' import ChatPrompt from "@/components/mobile/ChatPrompt.vue"; import ChatReply from "@/components/mobile/ChatReply.vue"; import { getSessionId, getUserToken } from "@/store/session"; import { checkSession } from "@/action/session"; import { getMobileTheme } from "@/store/system"; import ChatMidJourney from "@/components/mobile/ChatMidJourney.vue"; import QRCode from "qrcode"; import { ElMessage } from "element-plus"; import Clipboard from "clipboard"; import InviteList from "@/components/InviteList.vue"; import { createWebscoket, onSend, closeWs, start, onMessage } from '@/utils/socket.js' import { chineseChar2englishChar } from "@/utils/validate"; const winHeight = ref(0) const navBarRef = ref(null) const bottomBarRef = ref(null) const router = useRouter() const chatConfig = getChatConfig() const role = chatConfig ? chatConfig.role : '' const model = chatConfig ? chatConfig.model : '' const modelValue = chatConfig ? chatConfig.modelValue : "" const title = chatConfig ? chatConfig.title : '' const chatId = chatConfig ? chatConfig.chatId : '' const loginUser = ref(null) const listBoxHeight = window.innerHeight const inviteURL = ref("") const qrImg = ref("") const inviteChatCalls = ref(0) const inviteImgCalls = ref(0) const hits = ref(0) const regNum = ref(0) const rate = ref(0) const isLogin = ref(false) const token = ref("") const openId = ref("") const overlayshow = ref(false) onMounted(() => { var url = window.location.href; console.log(url) var regex = /[?&]token=([^&#]+)/; // 匹配 ? 或 & 后面跟 token= 开头的部分 var regexId = /[?&]openId=([^&#]+)/; // 匹配 ? 或 & 后面跟 token= 开头的部分 var match = url.match(regex); token.value = decodeURIComponent(match[1]); openId.value = decodeURIComponent(url.match(regexId)[1]); localStorage.setItem('token', token.value) localStorage.setItem('openId', openId.value) winHeight.value = document.body.offsetHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight let host = process.env.VUE_APP_WS_HOST createWebscoket(host + token.value, messagesCallBack) loading.value = false previousText.value = ''; canSend.value = true; activelyClose.value = false; chatData.value.push({ content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', type: "reply", id: randString(32), icon: '/images/logo.png', orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', }) }) const chatData = ref([]) const loading = ref(false) const finished = ref(false) const error = ref(false) checkSession().then(user => { loginUser.value = user }).catch(() => { router.push('/mobile') }) //取url中的参数值 const getQuery = (name) => { // 正则:[找寻'&' + 'url参数名字' = '值' + '&']('&'可以不存在) let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); let r = window.location console.log(r); if (r != null) { // 对参数值进行解码 return decodeURIComponent(r[2]); } return null; } //新消息监听 const messagesCallBack = (msg) => { let socketMsg = msg.msg.replace(/\ufeff/g, ""); let socketMsgTwo = socketMsg ? JSON.parse(socketMsg) : '' if (socketMsgTwo.msgType != -1 || socketMsgTwo.msgType == 200) { loading.value = true if (socketMsgTwo.contentUrl) { chatData.value.push({ type: "mj", key: randString(32), icon: '/images/logo.png', content: socketMsgTwo.contentUrl }) loading.value = false } // 将聊天框的滚动条滑动到最底部 nextTick(() => { document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight) // localStorage.setItem("chat_id", chat_id) }) enableInput() } } //发送socket消息到服务器 const sendMessagext = (data) => { onSend(JSON.stringify(data)) } //断开socket const closeWsTxt = () => { closeWs() } // 创建 socket 连接 const prompt = ref(''); const showStopGenerate = ref(false); // 停止生成 const showReGenerate = ref(false); // 重新生成 const previousText = ref(''); // 上一次提问 const lineBuffer = ref(''); // 输出缓冲行 const socket = ref(null); const activelyClose = ref(false); // 主动关闭 const canSend = ref(true); const connect = function () { let host = process.env.VUE_APP_WS_HOST createWebscoket(host + token.value, messagesCallBack) loading.value = false previousText.value = ''; canSend.value = true; activelyClose.value = false; chatData.value.push({ content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', type: "reply", id: randString(32), icon: '/images/logo.png', orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。', }) } const disableInput = (force) => { canSend.value = false; // loading.value = true; showReGenerate.value = false; showStopGenerate.value = !force; } const enableInput = () => { canSend.value = true; // loading.value = false; showReGenerate.value = previousText.value !== ""; showStopGenerate.value = false; } // 将聊天框的滚动条滑动到最底部 const scrollListBox = () => { document.getElementById('message-list-box').scrollTo(0, document.getElementById('message-list-box').scrollHeight + 46) } const sendMessage = () => { if (canSend.value === false) { showToast("AI 正在作答中,请稍后..."); return } if (prompt.value.trim().length === 0) { return false; }else{ loading.value = true } // 追加消息 chatData.value.push({ type: "prompt", id: randString(32), icon: '', content: renderInputText(prompt.value), created_at: new Date().getTime(), }); nextTick(() => { scrollListBox() }) disableInput(false) // socket.value.send(prompt.value); var data = { userId: openId.value, toUserId: openId.value, content: chineseChar2englishChar(prompt.value), msgType: 2 } //websocket 发送 sendMessagext(data) previousText.value = prompt.value; prompt.value = ''; return true; } const stopGenerate = () => { showStopGenerate.value = false; enableInput() } const reGenerate = () => { disableInput(false) const text = '重新生成上述问题的答案:' + previousText.value; // 追加消息 chatData.value.push({ type: "prompt", id: randString(32), icon: '', content: renderInputText(text) }); var data = { userId: openId.value, toUserId: openId.value, content: chineseChar2englishChar(prompt.value), msgType: 1 } // socket.value.send(text); sendMessagext(data) } const showShare = ref(false) const shareOptions = [ { name: '微信', icon: 'wechat' }, { name: '微博', icon: 'weibo' }, { name: '复制链接', icon: 'link' }, { name: '分享海报', icon: 'poster' }, ] const shareChat = () => { showShare.value = false router.push('/mobile/Invitation'); } </script> <style lang="stylus" scoped> @import "@/assets/css/mobile/chat-session.css" </style> <style> .wrapper { display: flex; align-items: center; justify-content: center; height: 100%; } .block { width: 120px; height: 120px; font-size: 20px; color: #1989fa; /* background-color: #fff; */ } </style>
//uniapp <template> <view class="main"> <u-navbar :fixed="true" :autoBack="false" @leftClick="goBack"></u-navbar> <web-view :src="url" @message="message"></web-view> </view> </template> <script> export default { data() { return { url: '', token: '', canBack: false, openId: '' } }, onLoad() { this.token = uni.getStorageSync('token'); this.openId = uni.getStorageSync('openid'); this.url = 'https://xxxxx.com/mobile?token=' + encodeURIComponent(this.token) + '&openId=' + encodeURIComponent(this.openId) console.log(this.url) }, methods: { message(event) { console.log(event.detail.data); }, }, } </script> <style> .main { width: 100%; height: 100vh; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。