欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue2+marked.js實現(xiàn)AI流式輸出的項目實踐

 更新時間:2025年04月02日 11:59:50   作者:夜闌臥聽風(fēng)吹雨,鐵馬冰河入夢來  
本文主要介紹了Vue2+marked.js實現(xiàn)AI流式輸出的項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

1、實現(xiàn)效果

AI流式輸出

2、實現(xiàn)流程

1、頁面內(nèi)容

<template>
  <div class="app-container">
    <!-- 聊天界面 -->
    <div class="chat-container">
      <!-- 消息展示區(qū)域 -->
      <div class="chat-box" ref="chatBox">
        <div
          v-for="message in messages"
          :key="message.id"
          class="message"
          :class="message.from === 'user' ? 'user-message' : 'ai-message'"
        >
          <div v-if="!message.content" class="chat-message waiting">

            <!-- 加載動畫,例如一個旋轉(zhuǎn)的圖標(biāo) -->
            <div class="loading-spinner"></div>
            容我思考片刻 !
          </div>
          <p v-else v-html="markMessage(message.content)"></p>
        </div>
      </div>
      <!-- 輸入框與發(fā)送按鈕 -->
      <div class="input-container">
        <el-input
          v-model="userInput"
          placeholder="請輸入消息..."
          clearable
          @keyup.enter.native="sendMessage"
          class="chat-input"
        />
        <el-button type="primary" icon="el-icon-send" @click="sendMessage" class="send-button">發(fā)送</el-button>
      </div>
    </div>
  </div>
</template>

循環(huán)展示消息內(nèi)容,根據(jù)是用戶發(fā)送消息和AI返回消息展示不同的樣式

添加了一個等待動畫

2、js部分與樣式

<script>
// api部分大家根據(jù)自己的前端框架自己封裝即可,分別調(diào)用后端的兩個controller
import {sendMessage, sendMessage1} from "@/api/ai";
import { Marked } from 'marked'
import { markedHighlight } from "marked-highlight";
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
import "highlight.js/styles/paraiso-light.css";

export default {
  data() {
    return {
      messages: [], // 消息記錄
      userInput: "你好", // 用戶輸入,默認(rèn)一開始發(fā)一個“你好”
      pollingActive: false, // 是否正在長輪詢
      isEnd: false, // 標(biāo)記是否結(jié)束輪詢
      currentAiMessageId: null, // 當(dāng)前正在回復(fù)的 AI 消息的 ID
      userMsgData: {}, // 用戶消息數(shù)據(jù)
    };
  },
  async mounted() {
    this.sendMessage();
  },
  // computed: {
  //   newMessages() {
  //     this.messages.forEach(message=>{
  //       message.content=this.markMessage(message.content)
  //       console.log(message.content)
  //     })
  //     return this.messages
  //   }
  // },
  methods: {
    markMessage(message) {
      message=message.replaceAll('\\n','\n')
      console.log('調(diào)用前'+message)
      const marked = new Marked(
        markedHighlight({
          pedantic: false,
          gfm: true, // 開啟gfm
          breaks: true,
          smartLists: true,
          xhtml: true,
          async: false, // 如果是異步的,可以設(shè)置為 true
          langPrefix: 'hljs language-', // 可選:用于給代碼塊添加前綴
          emptyLangClass: 'no-lang', // 沒有指定語言的代碼塊會添加這個類名
          highlight: (code) => {
                 return hljs.highlightAuto(code).value
          }
        })
      );
     let  markedMessage = marked.parse(message)
      console.log('調(diào)用了'+markedMessage)
      return markedMessage
    },
    sendMessage() {
      if (!this.userInput.trim()) return;

      // 添加用戶消息
      this.messages.push({
        id: Date.now(),
        content: this.userInput,
        from: "user",
      });

      this.userMsgData.content = this.markMessage(this.userInput);
      // send(this.userMsgData);

      // 清空輸入框
      this.userInput = "";

      // 添加 AI 回復(fù)占位
      let newAiMessage = {
        id: Date.now() + 1,
        content: "",
        from: "ai",
      };
      this.messages.push(newAiMessage);

      this.currentAiMessageId = newAiMessage.id;

      // 啟動輪詢
      // if (this.isEnd || !this.pollingActive) {
      //   this.isEnd = false;
      //   this.pollingActive = true;
      //   this.polling();
      // }
      this.polling()
    },
    async polling() {
      try {
        // 給定的字符串
        const response = await sendMessage1(this.userMsgData.content);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let buffer = ''; // 用于累積部分消息
        while (true) {
          const {done, value} = await reader.read();
          if (done) {
            this.isEnd = true;
            this.pollingActive = false;
            break
          }
          buffer = decoder.decode(value, {stream: true});
          this.processServerSentEvent(buffer);

        }
        // 流結(jié)束時處理可能剩余的部分消息
        // this.processServerSentEvent(buffer);
      } catch (e) {
        console.log(e.toString())
      }
    },
    processServerSentEvent(eventData, isFinal = false) {
      const lines = eventData.split('\n');
      let currentMessage = ''
      lines.forEach(line => {
        if (line.startsWith('data:')) {
          // 提取data字段的值(去掉前面的'data: ')
          let a = line.split(':')
          currentMessage += a[1];
        } else {
          currentMessage+=line.trim()
        }
      })
      this.addNewMessage(currentMessage)
    },
    addNewMessage(data) {
      if (data) {
        try {
          let newMessageContent = data;
          // 通過消息id獲取目前的AI輸入位置
          const aiMessage = this.messages.find(
            (msg) => msg.id === this.currentAiMessageId
          );
          // newMessageContent = this.markMessage(newMessageContent)
          if (aiMessage) {
            aiMessage.content = `${aiMessage.content}${newMessageContent}`;
          }
          this.scrollToBottom()
        } catch (error) {
          console.error('Failed to parse JSON:', error);
        }
      }
    },
    scrollToBottom() {
      const chatBox = this.$refs.chatBox;
      chatBox.scrollTop = chatBox.scrollHeight;
    }
  }
};
</script>

<style scoped>
.app-container {
  display: flex;
  height: 90vh;
  background-color: #f3f4f6;
  font-family: "Arial", sans-serif;
}

/* 聊天容器 */
.chat-container {
  flex: 1; /* 右側(cè)占比 */
  display: flex;
  flex-direction: column;
  border-left: 1px solid #ddd;
  background-color: #fff;
  overflow: hidden;
}

.chat-box {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
  background-color: #fafafa;
  display: flex;
  flex-direction: column;
}

/* 通用消息樣式 */
.message {
  margin: 10px 0;
  padding: 10px;
  max-width: 70%;
  word-wrap: break-word;
  border-radius: 8px;
}

/* 用戶消息:右對齊 */
.user-message {
  align-self: flex-end;
  background-color: #e0f7fa;
  text-align: left;
}

/* AI 消息:左對齊 */
.ai-message {
  align-self: flex-start;
  background-color: #f1f1f1;
  text-align: left;
}

/* 輸入框和發(fā)送按鈕 */
.input-container {
  display: flex;
  padding: 10px;
  border-top: 1px solid #e0e0e0;
  background-color: #f9f9f9;
}

.chat-input {
  flex: 1;
  margin-right: 10px;
}

.send-button {
  flex-shrink: 0;
}
/* 加載指示器的樣式 */
.loading-spinner {
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-left-color: #4caf50; /* 可以根據(jù)需要調(diào)整顏色 */
  border-radius: 50%;
  width: 20px;
  height: 20px;
  animation: spin 1s linear infinite;
  margin-right: 10px; /* 與文本之間留出一些空間 */
}

/* 定義旋轉(zhuǎn)動畫 */
@keyframes spin {
  to { transform: rotate(360deg); }
}
/* 聊天消息的基本樣式 */
.chat-message {
  padding: 10px;
  border-radius: 8px;
  margin: 5px 0;
  position: relative;
  display: flex;
  align-items: center;
}

/* 正在等待的消息樣式 */
.waiting {
  color: #777; /* 設(shè)置文本顏色 */
  background-color: #f0f0f0; /* 設(shè)置背景顏色 */
}
</style>

發(fā)送消息后,生成用戶消息和AI回復(fù)占位

給AI接口發(fā)送消息,發(fā)送消息后,獲取到響應(yīng),然后使用reader.read方法讀取內(nèi)容。

給AI接口發(fā)送消息

export async function sendMessage1(message) {
  console.log(message+"----")
  try {
    const response = await fetch( '你的接口路徑', {
      method: 'POST',
      body:message
    })
    return response
  } catch (error) {
    console.error('請求失敗:', error);
  }
}

因為是流式響應(yīng),所以連接不是一次響應(yīng)就直接斷開的,使用while(true)循環(huán)不斷從中獲取到響應(yīng)內(nèi)容,并將響應(yīng)的內(nèi)容解碼。

AI接口響應(yīng) content-type: text/event-stream;charset=utf-8是這樣的,不是平常用的application/json,這表明響應(yīng)體是一個服務(wù)器發(fā)送事件(Server-Sent Events,簡稱SSE)流。SSE 允許服務(wù)器向客戶端(通常是瀏覽器)推送實時更新,而無需客戶端輪詢服務(wù)器。

async polling() {
      try {
        // 給定的字符串
        const response = await sendMessage1(this.userMsgData.content);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const reader = response.body.getReader();
        const decoder = new TextDecoder('utf-8');
        let buffer = ''; // 用于累積部分消息
        while (true) {
          const {done, value} = await reader.read();
          if (done) {
            this.isEnd = true;
            this.pollingActive = false;
            break
          }
          buffer = decoder.decode(value, {stream: true});
          this.processServerSentEvent(buffer);

        }
        // 流結(jié)束時處理可能剩余的部分消息
        // this.processServerSentEvent(buffer);
      } catch (e) {
        console.log(e.toString())
      }

將解碼后內(nèi)容經(jīng)過處理后添加到messages數(shù)組中。

 processServerSentEvent(eventData, isFinal = false) {
      console.log('收到數(shù)據(jù):  '+eventData)
      const lines = eventData.split('\n');
      let currentMessage = ''
      lines.forEach(line => {
        if (line.startsWith('data:')) {
          // 提取data字段的值(去掉前面的'data: ')
          let a = line.split(':')
          currentMessage += a[1];
        } else {
          currentMessage+=line.trim()
        }
      })
      this.addNewMessage(currentMessage)
    },

3、marked.js和highlight.js

marked.js 是一個用于將 Markdown 文本轉(zhuǎn)換為 HTML 的 JavaScript 庫,而 highlight.js 是一個用于語法高亮的庫,它可以與 marked.js 一起使用來高亮 Markdown 中的代碼塊

安裝marked.js和hightlight.js然后導(dǎo)入

npm install marked
npm install highlight.js
import { Marked } from 'marked'
import { markedHighlight } from "marked-highlight";
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
import "highlight.js/styles/paraiso-light.css";
markMessage(message) {
      message=message.replaceAll('\\n','\n')
      // console.log('調(diào)用前'+message)
      const marked = new Marked(
        markedHighlight({
          pedantic: false,
          gfm: true, // 開啟gfm
          breaks: true,
          smartLists: true,
          xhtml: true,
          async: false, // 如果是異步的,可以設(shè)置為 true
          langPrefix: 'hljs language-', // 可選:用于給代碼塊添加前綴
          emptyLangClass: 'no-lang', // 沒有指定語言的代碼塊會添加這個類名
          highlight: (code) => {
                 return hljs.highlightAuto(code).value
          }
        })
      );
     let  markedMessage = marked.parse(message)
      // console.log('調(diào)用了'+markedMessage)
      return markedMessage
    },

message就是markdown格式的文本內(nèi)容

4、添加等待效果

當(dāng)消息內(nèi)容為空時,顯示等待動畫,不為空顯示消息內(nèi)容

<div v-if="!message.content" class="chat-message waiting">

            <!-- 加載動畫,例如一個旋轉(zhuǎn)的圖標(biāo) -->
            <div class="loading-spinner"></div>
            容我思考片刻 !
          </div>
          <p v-else v-html="markMessage(message.content)"></p>

等待樣式

/* 加載指示器的樣式 */
.loading-spinner {
  border: 4px solid rgba(0, 0, 0, 0.1);
  border-left-color: #4caf50; /* 可以根據(jù)需要調(diào)整顏色 */
  border-radius: 50%;
  width: 20px;
  height: 20px;
  animation: spin 1s linear infinite;
  margin-right: 10px; /* 與文本之間留出一些空間 */
}

/* 定義旋轉(zhuǎn)動畫 */
@keyframes spin {
  to { transform: rotate(360deg); }
}
/* 聊天消息的基本樣式 */
.chat-message {
  padding: 10px;
  border-radius: 8px;
  margin: 5px 0;
  position: relative;
  display: flex;
  align-items: center;
}

/* 正在等待的消息樣式 */
.waiting {
  color: #777; /* 設(shè)置文本顏色 */
  background-color: #f0f0f0; /* 設(shè)置背景顏色 */
}

5、引入marked.js報錯解決

引入marked.js后,打包工程后,代碼報錯如下

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|             cells.shift();
|         }
>         if (cells.length > 0 && !cells.at(-1)?.trim()) {
|             cells.pop();
|         }

大概意思就是沒有l(wèi)oader來處理新語法.?,

解決方案vue.config.js中configurewebpack中增加如下代碼

module: {

      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
          },
        },
        // 其他rules...
      ],

    },
configureWebpack: {
    // provide the app's title in webpack's name field, so that
    // it can be accessed in index.html to inject the correct title.
    name: name,
    output: {
      chunkFilename: 'static/js/[name].js'
      // chunkFilename: 'static/js/[name][contenthash].js'
    },
    resolve: {
      alias: {
        '@': resolve('src')
      }
    },
    module: {

      rules: [
        {
          test: /\.js$/,
          use: {
            loader: 'babel-loader',
          },
        },
        // 其他rules...
      ],

    },
  }

到此這篇關(guān)于Vue2+marked.js實現(xiàn)AI流式輸出的項目實踐的文章就介紹到這了,更多相關(guān)Vue2+marked.js AI流式輸出內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • vue3?hook重構(gòu)DataV的全屏容器組件詳解

    vue3?hook重構(gòu)DataV的全屏容器組件詳解

    這篇文章主要為大家介紹了vue3?hook重構(gòu)DataV的全屏容器組件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • el-radio-group中的area-hidden報錯的問題解決

    el-radio-group中的area-hidden報錯的問題解決

    本文主要介紹了el-radio-group中的area-hidden報錯的問題解決,下面就來介紹幾種解決方法,具有一定的參考價值,感興趣的可以了解一下
    2025-04-04
  • 如何使用Gitee Pages服務(wù) 搭建Vue項目

    如何使用Gitee Pages服務(wù) 搭建Vue項目

    這篇文章主要介紹了如何使用Gitee Pages服務(wù) 搭建Vue項目,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • VUE3頁面div如何點擊改變樣式

    VUE3頁面div如何點擊改變樣式

    這篇文章主要介紹了VUE3頁面div如何點擊改變樣式問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • vue開發(fā)移動端h5環(huán)境搭建的全過程

    vue開發(fā)移動端h5環(huán)境搭建的全過程

    在正式使用Vue進行移動端頁面開發(fā)前,需要做一些前置工作,下面這篇文章主要給大家介紹了關(guān)于vue開發(fā)移動端h5環(huán)境搭建的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • 利用WebStorm創(chuàng)建一個Vue項目的完整步驟

    利用WebStorm創(chuàng)建一個Vue項目的完整步驟

    WebStorm是一個非常適合學(xué)習(xí)和開發(fā)Vue項目的集成開發(fā)環(huán)境,下面這篇文章主要給大家介紹了關(guān)于利用WebStorm創(chuàng)建一個Vue項目的完整步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2024-06-06
  • vue中的循環(huán)遍歷對象、數(shù)組和字符串

    vue中的循環(huán)遍歷對象、數(shù)組和字符串

    這篇文章主要介紹了vue中的循環(huán)遍歷對象、數(shù)組和字符串,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • vue購物車插件編寫代碼

    vue購物車插件編寫代碼

    這篇文章主要為大家詳細(xì)介紹了vue購物車插件的編寫代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • antd vue 如何調(diào)整checkbox默認(rèn)樣式

    antd vue 如何調(diào)整checkbox默認(rèn)樣式

    這篇文章主要介紹了antd vue 如何調(diào)整checkbox默認(rèn)樣式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • vue項目中銷毀window.addEventListener事件監(jiān)聽解析

    vue項目中銷毀window.addEventListener事件監(jiān)聽解析

    這篇文章主要介紹了vue項目中銷毀window.addEventListener事件監(jiān)聽,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07

最新評論