vue3 实现 chatgpt 的打字机效果

这篇具有很好参考价值的文章主要介绍了vue3 实现 chatgpt 的打字机效果。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在做 chatgpt 镜像站的时候,发现有些镜像站是没做打字机的光标效果的,就只是文字输出,是他们不想做吗?反正我想做。于是我仔细研究了一下,实现了打字机效果加光标的效果,现在分享一下我的解决方案以及效果图

vue3 实现 chatgpt 的打字机效果

共识

首先要明确一点,chatgpt 返回的文本格式是 markdown 的,最基本的渲染方式就是把 markdown 文本转换为 HTML 文本,然后 v-html 渲染即可。这里的转换和代码高亮以及防 XSS 攻击用到了下面三个依赖库:

  • marked 将markdwon 转为 html
  • highlight 处理代码高亮
  • dompurify 防止 XSS 攻击

同时我们是可以在 markdown 中写 html 元素的,这意味着我们可以直接把光标元素放到最后!

将 markdown 转为 html 并处理代码高亮

先贴代码

MarkdownRender.vue

<script setup>
import {computed} from 'vue';
import DOMPurify from 'dompurify';
import {marked} from 'marked';
import hljs from '//cdn.staticfile.org/highlight.js/11.7.0/es/highlight.min.js';
import mdInCode from "@/utils/mdInCode"; // 用于判断是否显示光标

const props = defineProps({
  // 输入的 markdown 文本
  text: {
    type: String,
    default: ""
  },
  // 是否需要显示光标?比如在消息流结束后是不需要显示光标的
  showCursor: {
    type: Boolean,
    default: false
  }
})

// 配置高亮
marked.setOptions({
  highlight: function (code, lang) {
    try {
      if (lang) {
        return hljs.highlight(code, {language: lang}).value
      } else {
        return hljs.highlightAuto(code).value
      }
    } catch (error) {
      return code
    }
  },
  gfmtrue: true,
  breaks: true
})

// 计算最终要显示的 html 文本
const html = computed(() => {
  // 将 markdown 转为 html
  function trans(text) {
    return DOMPurify.sanitize(marked.parse(text));
  }
  
  // 光标元素,可以用 css 美化成你想要的样子
  const cursor = '<span class="cursor"></span>';
  if (props.showCursor) {
    // 判断 AI 正在回的消息是否有未闭合的代码块。
    const inCode = mdInCode(props.text)
    if (inCode) {
      // 有未闭合的代码块,不显示光标
      return trans(props.text);
    } else {
      // 没有未闭合的代码块,将光标元素追加到最后。
      return trans(props.text + cursor);
    }
  } else {
    // 父组件明确不显示光标
    return trans(props.text);
  }
})

</script>

<template>
  <!-- tailwindcss:leading-7 控制行高为1.75rem -->
  <div v-html="html" class="markdown leading-7">
  </div>
</template>

<style lang="postcss">
/** 设置代码块样式 **/
.markdown pre {
  @apply bg-[#282c34] p-4 mt-4 rounded-md text-white w-full overflow-x-auto;
}
.markdown code {
  width: 100%;
}

/** 控制段落间的上下边距 **/
.markdown p {
  margin: 1.25rem 0;
}
.markdown p:first-child {
  margin-top: 0;
}

/** 小代码块样式,对应 markdown 的 `code` **/
.markdown :not(pre) > code {
  @apply bg-[#282c34] px-1 py-[2px] text-[#e06c75] rounded-md;
}

/** 列表样式 **/
.markdown ol {
  list-style-type: decimal;
  padding-left: 40px;
}
.markdown ul {
  list-style-type: disc;
  padding-left: 40px;
}

/** 光标样式 **/
.markdown .cursor {
  display: inline-block;
  width: 2px;
  height: 20px;
  @apply bg-gray-800 dark:bg-gray-100;
  animation: blink 1.2s step-end infinite;
  margin-left: 2px;
  vertical-align: sub;
}
@keyframes blink {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
</style>

可以发现最基本的 markdown 显示还是挺简单的,话就不多说了,都在注释里。

我想你也许对判断消息中的代码块是否未闭合更感兴趣,那么就继续看下去吧!

代码块是否未闭合

markdown 有两种代码块,一种是 `code` ,另一种是 ``` code ```,我叫他小代码块和大代码块。

一开始我是想用正则去判断的,但是奈何有点复杂,我实在想不出应该如何去编写正则,让 chatgpt 写的正则也会判断失败,而且还要考虑到转义符,就算写出了正则,估计也会很复杂和难以维护。

经过短暂的苦思冥想后,我想到了之前在 《Vue.js设计与实现》 中看到的用有限元状态机解析 html 文本的方案。

这里有请 chatgpt 简单介绍一下有限元状态机:

有限元状态机(finite element state machine)是一种基于状态机的建模技术,它可以将系统的行为描述为一组状态和状态之间的转换。每个状态可以对应系统内的一种状态,而状态之间的转换则表示系统内部的转换过程。有限元状态机还可以用于系统的可靠性分析和测试,同时也可以作为软件工程中的一种建模工具,帮助代码实现、测试和调试等方面。

在实际应用中,有限元状态机通常是通过自动化工具生成,并且可以用于控制系统、网络通信协议、软件接口等方面的设计和测试。由于有限元状态机具有自动化生成和完备性检查等优点,因此它已经成为了一种广泛应用的建模与测试工具。

请站在web前端的视角下进行介绍

从前端开发的角度来看,有限元状态机可以用于构建复杂的交互式Web应用程序。在Web应用程序中,有限元状态机可以描述用户界面的不同状态(例如,登录、注册、未登录等),以及状态之间的转换(例如,从登录状态转换到注册状态)。

通常情况下,前端工程师使用JavaScript实现有限元状态机,包括定义状态、定义状态之间的转换规则、处理状态转换时的行为等。使用有限元状态机可以帮助前端工程师更好地掌控应用程序的状态,从而减少代码中的复杂条件分支和无法预测的行为。同时,有限元状态机也可以帮助前端团队共同理解应用程序的状态和转换规则,从而更好地协作开发和维护Web应用程序。

总之,有限元状态机是一种非常有用的前端开发技术,可以帮助前端工程师更好地构建和管理Web应用程序的状态和行为,提高应用程序的可靠性和用户体验。

回到正题,我可以一点一点的从头开始去解析 markdown 文本。想象这么一个简单的状态转换流程:

  • 初始状态为文本状态。
  • 遇到代码块标记,文本状态转换到代码块开始状态。
  • 再次遇到代码块标记,从代码块开始状态转换到文本状态。

不过现实要更复杂一点,我们有小代码块和大代码块。有限元状态机的妙处就在这里,当处在小代码块状态的时候,我们不需要操心大代码块和正常文本的事,他的下一个状态只能是遇到小代码块的闭合标签,进入文本状态。

理解了这些,再来看我的源码,才会发现他的精妙。

const States = {
    text: 0, // 文本状态
    codeStartSm: 1, // 小代码块状态
    codeStartBig: 2, // 大代码块状态
}

/**
 * 判断 markdown 文本中是否有未闭合的代码块
 * @param text
 * @returns {boolean}
 */
function isInCode(text) {
    let state = States.text
    let source = text
    let inStart = true // 是否处于文本开始状态,即还没有消费过文本
    while (source) { // 当文本被解析消费完后,就是个空字符串了,就能跳出循环
        let char = source.charAt(0) // 取第 0 个字
        switch (state) {
            case States.text:
                if (/^\n?```/.test(source)) {
                    // 以 ```或者 \n```开头。表示大代码块开始。
                    // 一般情况下,代码块前面都需要换行。但是如果是在文本的开头,就不需要换行。
                    if (inStart || source.startsWith('\n')) {
                        state = States.codeStartBig
                    }
                    source = source.replace(/^\n?```/, '')
                } else if (char === '\\') {
                    // 遇到转义符,跳过下一个字符
                    source = source.slice(2)
                } else if (char === '`') {
                    // 以 ` 开头。表示小代码块开始。
                    state = States.codeStartSm
                    source = source.slice(1)
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                inStart = false
                break
            case States.codeStartSm:
                if (char === '`') {
                    // 遇到第二个 `,表示代码块结束
                    state = States.text
                    source = source.slice(1)
                } else if (char === '\\') {
                    // 遇到转义符,跳过下一个字符
                    source = source.slice(2)
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                break
            case States.codeStartBig:
                if (/^\n```/.test(source)) {
                    // 遇到第二个 ```,表示代码块结束
                    state = States.text
                    source = source.replace(/^\n```/, '')
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                break
        }
    }
    return state !== States.text
}

export default isInCode

到这里,就已经实现了一个 chatgpt 消息渲染了。喜欢的话点个赞吧!谢谢!文章来源地址https://www.toymoban.com/news/detail-453691.html

到了这里,关于vue3 实现 chatgpt 的打字机效果的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微信小程序-接入sse数据流并实现打字机效果( ChatGPT )

    从流中获取的数据格式如下 小程序调用SSE接口 我这边接收到的数据类型为Uint8Array,需要处理成text文本(如上图) 使对话有打字机效果 参考自:小程序实现 ChatGPT 聊天打字兼自动滚动效果 完整代码

    2024年04月09日
    浏览(60)
  • ChatGPT 打字机效果原理

    在初次使用 ChatGPT 时,我就被打字机的视觉效果吸引。总是感觉似曾相识,因为经常在一些科幻电影中看到,高级文明回传的信息在通讯设备的屏幕上以打字机效果逐步出现,在紧张的氛围下,输出人类可读的内容,拉动着观众的神经,一步步将故事情节推向高潮。 在很早之

    2024年02月04日
    浏览(33)
  • 大模型问答助手前端实现打字机效果

    随着现代技术的快速发展,即时交互变得越来越重要。用户不仅希望获取信息,而且希望以更直观和实时的方式体验它。这在聊天应用程序和其他实时通信工具中尤为明显,用户习惯看到对方正在输入的提示。 ChatGPT,作为 OpenAI 的代表性产品之一,不仅为用户提供了强大的自

    2024年02月08日
    浏览(27)
  • 记录--20行js就能实现逐字显示效果???-打字机效果

    横版 竖版 可以看到文字是一段一段的并且独占一行,使用段落标签p表示一行 一段文字内,字是一个一个显示的,所以这里每一个字都用一个span标签装起来 每一个字都是从透明到不透明的过渡效果,使用css3的过渡属性transition让每个字都从透明过渡到不透明 这里只需要一个

    2024年02月06日
    浏览(28)
  • 前端发送Fetch请求实现流式请求、模拟打字机效果等

    前端需要接收后端的流式返回数据,并实时渲染。 普通的xhr请求都是等http协议数据包一次性返回之后才渲染,类似于ChatGPT的Http接口内容类型为text/event-stream。这种内容类型需要与浏览器建立持久连接并持续监听服务器返回的数据。 npm 方式安装类库 使用 调用 fetchEventSource

    2024年02月13日
    浏览(29)
  • Unity Text文本实现打字机(一个一个出来)的效果

    Unity Text文本要实现打字机,即一个个文字出来的效果,可以通过代码把text文本字符串拆成一个个字符然后添加到文本中。 具体实现: 新建一个控制脚本:TypewriteController.cs,并编写以下代码: 此控制脚本先把脚本文本获取后赋给一个字符串变量,然后置空文本内容,再通过

    2024年01月25日
    浏览(32)
  • 【JS真好玩】自动打字机效果

    大家好,今天实现一个自动打字机效果,旨在实现一些网上很小的demo样例,通过每一个小demo能够巩固一下我们的前端基础知识。 今天,主要利用定时器、flex布局实现一个自动打字机效果。 效果展示 : 考察 : flex布局、定时器、字符串 建议用时20~35min 我们主要把自动打字

    2024年02月10日
    浏览(29)
  • 仅使用 CSS 创建打字机动画效果

    创建打字机效果比您想象的要容易。虽然实现这种效果的最常见方法是使用 JavaScript,但我们也可以使用纯 CSS 来创建我们的打字机动画。 在本文中,我们将了解如何仅使用 CSS 创建打字机动画效果。它简单、漂亮、容易。我们还将看看使用 CSS 与 JavaScript 创建这种效果的利弊

    2024年02月13日
    浏览(38)
  • 聊聊大模型"打字机"效果的背后技术——SSE

    转载请注明出处:https://www.cnblogs.com/zhiyong-ITNote SSE:Server Sent Event;服务器发送事件。 Server-Sent Events(SSE)是一种由服务器向客户端推送实时数据的技术。它是构建基于事件的、服务器到客户端的通信的一种方法,特别适用于需要实时更新和推送信息的应用场景,如实时通知

    2024年03月27日
    浏览(38)
  • 【CSS3】CSS3 动画 ⑤ ( 动画速度曲线 | 设置动画步长 | 动画匀速执行 | 动画分 2 步执行 | 使用动画步长实现打字机效果 )

    CSS3 样式中 , 设置 动画速度曲线 的属性是 animation-timing-function 属性 ; animation-timing-function 属性定义了动画从 初始 CSS 样式 变为 结束状态 时 所消耗的时间 ; animation-timing-function 属性常用 属性值 如下 : linear : 动画在整个执行过程中速度都是匀速的 ; ease : 默认属性值 , 动画首先

    2024年02月13日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包