目次
共识
将 markdown 转为 html 并处理代码高亮
代码块是否未闭合
ホームページ ウェブフロントエンド Vue.js chatgpt のタイプライター効果を実現する vue3 の詳細な例

chatgpt のタイプライター効果を実現する vue3 の詳細な例

Apr 18, 2023 pm 03:40 PM
フロントエンド vue.js chatgpt

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

Kapture 2023-04-14 at 14.02.32.gif

共识

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

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

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

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

先贴代码

MarkdownRender.vue

<script setup>
import {computed} from &#39;vue&#39;;
import DOMPurify from &#39;dompurify&#39;;
import {marked} from &#39;marked&#39;;
import hljs from &#39;//cdn.staticfile.org/highlight.js/11.7.0/es/highlight.min.js&#39;;
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 = &#39;<span></span>&#39;;
  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>
/** 设置代码块样式 **/
.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(&#39;\n&#39;)) {
                        state = States.codeStartBig
                    }
                    source = source.replace(/^\n?```/, &#39;&#39;)
                } else if (char === &#39;\\&#39;) {
                    // 遇到转义符,跳过下一个字符
                    source = source.slice(2)
                } else if (char === &#39;`&#39;) {
                    // 以 ` 开头。表示小代码块开始。
                    state = States.codeStartSm
                    source = source.slice(1)
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                inStart = false
                break
            case States.codeStartSm:
                if (char === &#39;`&#39;) {
                    // 遇到第二个 `,表示代码块结束
                    state = States.text
                    source = source.slice(1)
                } else if (char === &#39;\\&#39;) {
                    // 遇到转义符,跳过下一个字符
                    source = source.slice(2)
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                break
            case States.codeStartBig:
                if (/^\n```/.test(source)) {
                    // 遇到第二个 ```,表示代码块结束
                    state = States.text
                    source = source.replace(/^\n```/, &#39;&#39;)
                } else {
                    // 其他情况,直接消费当前字符
                    source = source.slice(1)
                }
                break
        }
    }
    return state !== States.text
}

export default isInCode
ログイン後にコピー

到这里,就已经实现了一个 chatgpt 消息渲染了。喜欢的话点个赞吧!谢谢!

以上がchatgpt のタイプライター効果を実現する vue3 の詳細な例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

ChatGPT では、無料ユーザーが 1 日あたりの制限付きで DALL-E 3 を使用して画像を生成できるようになりました ChatGPT では、無料ユーザーが 1 日あたりの制限付きで DALL-E 3 を使用して画像を生成できるようになりました Aug 09, 2024 pm 09:37 PM

DALL-E 3は、前モデルより大幅に改良されたモデルとして2023年9月に正式導入されました。これは、複雑な詳細を含む画像を作成できる、これまでで最高の AI 画像ジェネレーターの 1 つと考えられています。ただし、発売当初は対象外でした

携帯電話にchatgptをインストールする方法 携帯電話にchatgptをインストールする方法 Mar 05, 2024 pm 02:31 PM

インストール手順: 1. ChatGTP ソフトウェアを ChatGTP 公式 Web サイトまたはモバイル ストアからダウンロードします; 2. それを開いた後、設定インターフェイスで言語を中国語を選択します; 3. ゲーム インターフェイスでヒューマン マシン ゲームを選択し、中国スペクトル; 4 . 起動後、チャット ウィンドウにコマンドを入力してソフトウェアを操作します。

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ Mar 16, 2024 pm 12:09 PM

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ 今日のインターネットの急速な発展の時代において、フロントエンド開発はますます重要になっています。 Web サイトやアプリケーションのエクスペリエンスに対するユーザーの要求がますます高まっているため、フロントエンド開発者は、より効率的で柔軟なツールを使用して、応答性の高いインタラクティブなインターフェイスを作成する必要があります。フロントエンド開発の分野における 2 つの重要なテクノロジーである PHP と Vue.js は、組み合わせることで完璧なツールと見なされます。この記事では、PHP と Vue の組み合わせと、読者がこれら 2 つをよりよく理解し、適用できるようにするための詳細なコード例について説明します。

中国でもchatgptは使えますか? 中国でもchatgptは使えますか? Mar 05, 2024 pm 03:05 PM

chatgpt は中国でも使用できますが、香港やマカオでも登録できません。ユーザーが登録したい場合は、外国の携帯電話番号を使用して登録できます。登録プロセス中にネットワーク環境を切り替える必要があることに注意してください。外国のIP。

フロントエンドの面接官からよく聞かれる質問 フロントエンドの面接官からよく聞かれる質問 Mar 19, 2024 pm 02:24 PM

フロントエンド開発のインタビューでは、HTML/CSS の基本、JavaScript の基本、フレームワークとライブラリ、プロジェクトの経験、アルゴリズムとデータ構造、パフォーマンスの最適化、クロスドメイン リクエスト、フロントエンド エンジニアリング、デザインパターン、新しいテクノロジーとトレンド。面接官の質問は、候補者の技術スキル、プロジェクトの経験、業界のトレンドの理解を評価するように設計されています。したがって、候補者はこれらの分野で自分の能力と専門知識を証明するために十分な準備をしておく必要があります。

SearchGPT: Open AI が独自の AI 検索エンジンで Google に対抗 SearchGPT: Open AI が独自の AI 検索エンジンで Google に対抗 Jul 30, 2024 am 09:58 AM

オープン AI がついに検索に進出します。サンフランシスコの同社は最近、検索機能を備えた新しい AI ツールを発表した。今年 2 月に The Information によって初めて報告されたこの新しいツールは、まさに SearchGPT と呼ばれ、次のような機能を備えています。

Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Jan 19, 2024 am 08:37 AM

Django は、迅速な開発とクリーンなメソッドを重視した Python で書かれた Web アプリケーション フレームワークです。 Django は Web フレームワークですが、Django がフロントエンドなのかバックエンドなのかという質問に答えるには、フロントエンドとバックエンドの概念を深く理解する必要があります。フロントエンドはユーザーが直接対話するインターフェイスを指し、バックエンドはサーバー側プログラムを指し、HTTP プロトコルを通じてデータと対話します。フロントエンドとバックエンドが分離されている場合、フロントエンドとバックエンドのプログラムをそれぞれ独立して開発して、ビジネス ロジックとインタラクティブ効果、およびデータ交換を実装できます。

専用アプリのリリースにより、ChatGPT が macOS で利用可能になりました 専用アプリのリリースにより、ChatGPT が macOS で利用可能になりました Jun 27, 2024 am 10:05 AM

Open AI の ChatGPT Mac アプリケーションは、ここ数か月間 ChatGPT Plus サブスクリプションを持つユーザーのみに限定されていましたが、現在は誰でも利用できるようになりました。最新の Apple S を持っている限り、アプリは他のネイティブ Mac アプリと同じようにインストールされます。

See all articles