暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

实现chatgpt聊天机器人打字机效果

原创 浮游 2023-09-14
297

思路:markdown-it和highlight是为了解决打字机过程中,写代码时,没有高亮效果的,同时还解决了,在打字过程中,滚动条没有随内容而自动滚动的问题

<template> <div class="wd1200"> <div class="expand"> <div class="shadow mt10"> <div class="pad2030 flex between border-b"> <div class="flex-se"> <div class="f22 mr20">智能咨询</div> </div> </div> <div ref="scrollable" @scroll="handleScroll" class="pad2030 bfa scroll msg-container border-b f14 scrollbar"> <div class="flex-st mb10"> <Avatar name="数据库智能助手小墨" avatar="https://oss-emcsprod-public.modb.pro/image/avatar/401097_1676426340830.jpeg" class="wd50 mr10"></Avatar> <div> <div class="mt5 c6">数据库智能助手小墨</div> <div class="msg-box pad10 bg-active mt10"> <div>Hi,我是数据库智能助手小墨,您可以向我描述问题。紧急问题请联系墨天轮小助手:modb666。</div> <img class="block mt8" src="https://js-cdn.modb.cc/image/modb666.jpg" width="80px" alt="暂无图片"> </div> </div> </div> <div class="mb10" v-for="(item, idx) in historyList" :key="idx"> <div class="vr" v-if="item.question"> <div class="msg-box border-own pad10 bg-blue cf p-wrap">{{ item.question }}</div> </div> <div class="flex-st" v-if="item.answer"> <Avatar name="数据库智能助手小墨" avatar="https://oss-emcsprod-public.modb.pro/image/avatar/401097_1676426340830.jpeg" class="wd50 mr10"></Avatar> <div> <div class="mt5 mb10"> <span class="c6 mr10">数据库智能助手小墨</span> </div> <div class="msg-box pad10 bg-active markdown-body" v-html="item.answer"></div> </div> </div> </div> <div class="mb10" v-if="currentText || btnLoading"> <div class="flex-st"> <Avatar name="数据库智能助手小墨" avatar="https://oss-emcsprod-public.modb.pro/image/avatar/401097_1676426340830.jpeg" class="wd50 mr10"></Avatar> <div> <div class="mt5 mb10"> <span class="c6 mr10">数据库智能助手小墨</span> </div> <div> <div v-if="currentText" class="msg-box pad10 bg-active markdown-body" v-html="markdownText"></div> <div v-else class="msg-box pad10 bg-active markdown-body">正在努力思考,请耐心等待...</div> </div> </div> </div> </div> </div> <div class="pad2030"> <el-input class="no-border" v-model.trim="consultInfo.question" :rows="3" type="textarea" @keyup.enter.native="submitConsult" :disabled="btnLoading" placeholder="请清晰描述一下您的问题,如:有哪些课程"/> <div class="vr mt20"> <div class="emcs-btn bold" @click="submitConsult">发送留言</div> </div> </div> </div> </div> </div> </template> <script> import { mapGetters } from 'vuex' import { loginmixin } from '@/mixins' import { cbSuccess, isMobile } from '@/utils' import { getMoaiApi, saveMoaiApi } from '@/apis' import MarkdownIt from 'markdown-it' import mdKatex from '@traptitech/markdown-it-katex' import mila from 'markdown-it-link-attributes' import hljs from 'highlight.js' function highlightBlock(str, lang) { return `<pre class="code-block-wrapper"><div class="code-block-header"></div><code class="hljs code-block-body ${lang}">${str}</code></pre>` } const mdi = new MarkdownIt({ html: false, linkify: true, highlight(code, language) { const validLang = Boolean(language && hljs.getLanguage(language)); if (validLang) { const lang = language || ''; return highlightBlock(hljs.highlight(code, { language: lang }).value, lang); } return highlightBlock(hljs.highlightAuto(code).value, ''); }, }) mdi.use(mila, { attrs: { target: '_blank', rel: 'noopener' } }) mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' }) export default { name: 'consult', head: { title: '智能咨询', }, data () { return { consultInfo: { question: '', }, records: [], historyList: [], currentText: '', mdText: '', currentQuestion: '', btnLoading: false, isAutoScroll: true, total: 0, threshold: 100, // 是否正在进行上啦加载调用 isFetching: false, // 是否正在进行问题回答 isReadText: false, params: { pageNum: 1, pageSize: 5 }, // 记录上一次滚动的高度 lashScrollTop: 0, } }, computed: { ...mapGetters(['isSys']), markdownText() { const value = this.currentText || ''; this.mdText = mdi.render(value) return mdi.render(value); } }, mixins: [loginmixin], methods: { async fetchData () { let { data } = await getMoaiApi(this.params) let _historyList = data.operateCallBackObj.list for (const item of _historyList) { item.answer = mdi.render(item.answer) } this.historyList = _historyList this.total = data.operateCallBackObj.total }, async submitConsult(){ this.scrollToBottom(); this.isReadText = true; // 开始发送请求相当于正在进行打印 if (!this.$checkLogin()) return let _consultInfo = JSON.parse(JSON.stringify(this.consultInfo)) if(this.btnLoading || !_consultInfo.question) return this.btnLoading = true let myInfo = { action_id: 'my', question: _consultInfo.question } this.currentQuestion = _consultInfo.question this.historyList.push(myInfo) this.consultInfo.question = '' let { data } = await saveMoaiApi(_consultInfo) let _this = this if(!data.success && data.operateMessage == '发布频率太快了,先休息一下吧'){ let operateCallBackObj = { answer: '发布频率太快了,先休息一下吧', question: "_consultInfo.question" } data.operateCallBackObj = operateCallBackObj _this.readWriter(data, 100) }else{ cbSuccess(data, _ => { _this.readWriter(data, 100) }) } }, async readWriter(data, delay) { let _this = this let _consult = data.operateCallBackObj let _text = _consult.answer for (let i = 0; i < _text.length; i++) { _this.currentText += _text[i]; this.isReadText = true; // 正在进行问题回答 if (this.isAutoScroll) { // 只有在自动滚动模式下,才自动滚动 await this.scrollToBottom(); } await this.sleep(delay); if(i == _text.length - 1) { _this.currentText = '' this.btnLoading = false let _actions = { answer: _this.mdText } _this.historyList.push(_actions) this.isReadText = false; // 问题回答结束 } } }, sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, async scrollToBottom() { await this.$nextTick(); // 判断当前对话框是否产生滚动条 const scrollable = this.$refs.scrollable if(scrollable.scrollHeight <= scrollable.clientHeight) { if (this.total > this.historyList.length) { await this.fetchMoreData(); } } if (this.$refs.scrollable) this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight; }, handleScroll() { // 新的滚动处理方法 const scrollable = this.$refs.scrollable; const scrollTop = scrollable.scrollTop; // 处理上拉加载逻辑 if (scrollTop <= 240 && !this.isReadText) { // 判断滚轮是往下滑动 如果是往下滑动不做处理 if (scrollTop < this.lashScrollTop && this.total > this.historyList.length) { this.fetchMoreData(); } } this.lashScrollTop = scrollTop; const distanceToBottom = scrollable.scrollHeight - scrollable.scrollTop - scrollable.clientHeight; if (distanceToBottom <= this.threshold) { this.isAutoScroll = true; // 滚动到底部,启动自动滚动 } else { this.isAutoScroll = false; // 用户手动滚动,停止自动滚动 } }, // 上拉加载函数 async fetchMoreData() { if (this.isFetching) return // 利用变量控制 防止重复触发上拉加载 if (this.total === this.historyList.length) return this.isFetching = true; // 开始进行上拉加载 // 这里写上拉加载接口 接口回调完成之后记得重置状态 this.params.pageNum++ let { data } = await getMoaiApi(this.params) // 模拟请求状态 let _historyList = data.operateCallBackObj.list for (const item of _historyList) { item.answer = mdi.render(item.answer) } this.historyList.unshift(..._historyList) if (this.params.pageNum > 2) { this.$refs.scrollable.scrollTop = 200; } this.isFetching = false; // 上拉加载结束 }, submitItem(val){ this.consultInfo.question = val this.submitConsult() } }, async mounted(){ this.isMobile = isMobile() await this.fetchData() this.scrollToBottom() } } </script> <style lang="stylus" scoped> .wd1200{ min-width 1200px box-sizing border-box margin 0 auto } .msg-box { max-width: 700px; display: inline-block; border-radius: 0px 16px 16px 16px; text-align: left; } .border-own { border-radius: 16px 16px 0 16px } .bg-blue{ background-color: #4285f4 } .bg-active { background-color: rgba(66,133,244,.08); } .border-own { border-radius: 16px 16px 0px 16px; } .msg-container { height: 60vh; } .scroll { overflow-y: auto; -ms-overflow-style: none; scrollbar-width: none; } .markdown-body{ background-color rgba(66,133,244,0.08) } @media screen and (max-width 768px) { .wd1200{ margin 0 auto min-width 0px max-width: 100% } .shadow{ margin-top 0 } } </style>
最后修改时间:2024-02-05 12:37:51
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论