動態

詳情 返回 返回

流式輸出-流式渲染-02 - 動態 詳情

現在有很多框架實現了流式渲染,我在這裏例舉幾個:
React框架
● Ant-Design-X

Vue框架
● Element-Plus-X
● MateChat (PC/H5雙端兼容)

H5移動端
● ChatUI-React

這裏以MateChat框架做示例,MateChat是一個獨立的AI對話組件,不與其它UI框架關聯,可直接引入項目使用。

一般的流式渲染分兩種模塊
1、深度思考
2、正文回答

我們拿到SSE接口返回的數據後,如果有深度思考,數據裏一般會有一個<think>標籤,標籤內的就是深度思考的內容,一般組件是支持識別深度思考標記的,無需我們過多處理。

let content = '<think>深度思考的內容</think>正文回覆,今天天氣不錯'

我們現在做一個真實的流式渲染示例
1、SSE消息回覆
SSE回來的消息是序列化過的,需要反序列化處理

    onmessage(event: any) {
      try {
        // 反序列化
        let data = JSON.parse(event.data);
        
        // 完整的文本以分塊的方式輸出
        flowAddText(data.answer, data.messageId);
 
      } catch (err) {
        console.log('反序列化出錯', err);
      }
    },

const flowAddText = async (text: string, messageId: string) => {
  // 拿到倒數第一條信息
  const lastChat = chatList.value.at(-1);
  // 添加流式文本
  lastChat.content += text;
  // 添加messageId
  lastChat.messageId = messageId;
  // 最後一條加載中false,因為開始回覆了
  lastChat.loading = false;
  // 未完整回覆完畢
  lastChat.contentLoading = false;
};

在這裏要注意一點,頁面展示的對話氣泡需要我們手動添加,我們現在用的MateChat框架,字段以框架説明為準,但最終消息都是要添加到chatList中做渲染的,我這裏封裝了一個hook,基本上AI對話所需的狀態都能覆蓋了,你可以根據實際情況做修改

import AIavatar from '@/assets/image/AI-avatar.png';
import { v4 as uuidv4 } from 'uuid';

/**
 * AI對話通用方法
 * @returns {startChat}
 */
export const useChatMethods = () => {

  /**
   * 添加自身對話氣泡
   * @param {string} text 自身對話內容
   * @returns 氣泡元信息
   */
  const addSelfChat = (text: string) => {
    return {
      id: uuidv4(),
      avatarConfig: {
        imgSrc: 'https://tdesign.gtimg.com/site/avatar.jpg'
      }, // 頭像
      from: 'user', // 用户角色
      content: text, // 內容
      loading: false // 加載中
    };
  };

  /**
   * 添加AI對話氣泡
   *  @param {string} answer ai對話內容
   *  @param {boolean} loading 是否加載中
   *  @param {boolean} contentEnd 回答結束
   *  @param {boolean} feedback 點贊狀態
   *  @param {boolean} messageId 消息id
   *  @returns 氣泡元信息
   */
  const addAIChat = (answer: string, loading: boolean, contentEnd: boolean, feedback: any, messageId?: string) => {
    return {
      id: uuidv4(),
      messageId,
      avatarConfig: {
        imgSrc: AIavatar
      }, // 頭像
      feedback: {
        rating: !feedback ? null : feedback.rating
      }, // 點贊狀態
      from: 'assistant', // 角色
      content: answer, // 內容
      loading, // 加載中
      contentEnd, // 回答結束
      expand: true // 默認展開思考
    };
  };

  return {
    addSelfChat,
    addAIChat
  };
};

在頁面中使用的流程
1、用户發送消息,添加自身氣泡
2、添加AI氣泡,AI氣泡狀態loading中,然後建立SSE連接
3、SSEonmessage開始返回消息,AI氣泡loading停止,開始流式輸出
4、SSE返回完畢,重置所有你自定義的標記和置空所有連接。

const onSend = (text: string) => {
  // 先添加自身氣泡
  let myBubble = useChatMethods().addSelfChat(text);
  chatList.value.push(myBubble);

  // 然後添加建立SSE連接
  startSSE();
}

const startSSE = () => {
  // 連接前可以先清空所有狀態,避免重複連接
  resetSSE(); // 你的自定義清空函數

  // 添加AI氣泡
  let myBubble = useChatMethods().addAIChat('', true, false, null, '');
  chatList.value.push(myBubble);

  // 建立SSE連接,你自己的邏輯......
  // fetchEventSource()
}

下面是頁面結構,每個內容都用氣泡包裹,McBubble有一個默認插槽,可以自定義氣泡內的內容

<div class="chat-content">
  <template v-for="(msg, index) in chatList" :key="msg.id">
    <!-- 用户對話 -->
    <McBubble 
      v-if="msg.from == 'user'" 
      :variant="'bordered'" 
      :content="msg.content" 
      :align="'right'" 
      :avatar-config="msg.avatarConfig">
    </McBubble>
    <!-- ai對話 -->
    <McBubble 
      v-else 
      :loading="msg.loading" 
      :avatar-config="msg.avatarConfig" 
      :variant="'bordered'">
      <!-- 深度思考的小框 -->
      <div 
        v-if="msg.content.includes('<think>')" 
        class="think-toggle-btn" 
        @click="msg.expand = !msg.expand">
        <LoadingIcon 
          class="think-loading" 
          v-if="!msg.content.includes('</think>') && !msg.contentEnd" />
        <SvgIcon 
          :name="'think-end'" 
          :size="20" 
          v-else />
        <span 
          class="think-text">
          {{ isThinkEnd(msg.content) ? '已深度思考' : msg.contentEnd ? '已暫停思考' : '思考中...'}}
        </span>
        <i :class="['arrow', msg.expand ? 'icon-chevron-up-2' : 'icon-chevron-down-2']"></i>
      </div>
      <!-- McMarkdown渲染 -->
      <McMarkdownCard :enable-think="true" :content="msg.content" theme="light"></McMarkdownCard>
    </McBubble>
  </template>
</div>

Add a new 評論

Some HTML is okay.