当前位置: 首页 > article >正文

智能AI流式输出的前端展现过程

写在前面

这篇文章主要介绍一下我们在对接市面上智能AI的时候,AI返回给我们的信息,前端如何正确的接收并展现,我们都知道豆包那种效果是比较合理的,打字机一样,好像真的是在思考中给你回答一样,所以今天就简单介绍一下这种效果是怎么实现的。

websocket链接效果

在这里插入图片描述

一般AI问答都是通过这种实效性比较高的长链接,前面的文章介绍过关于这种非长链接的形式的优缺点,自行查看,那么接收到回答之后我们需要使用打字机效果进行处理并展现出来,同时还要注意在展现的过程中不可以再触发第二次的会话,否则就会出现上一次的长链接还没有结束,本次的又来了,那么回答的次序就会变的无法控制。

代码实现
// WebSocket消息接口定义
interface WebSocketMessage {
  code: string;      // 消息代码
  isSuccess: boolean; // 消息是否成功
  data: string;      // 消息数据
}

// 请求参数接口定义
interface RequestParameters {
  [key: string]: any; // 任意键值对
}

/**
 * WebSocket管理器类
 * 使用单例模式管理WebSocket连接
 */
class WebSocketManager {
  private static instance: WebSocketManager;              // 单例实例
  private socket: WebSocket | null = null;               // WebSocket实例
  private messageQueue: string[] = [];                   // 消息队列
  private charQueue: string[] = [];                      // 字符队列(用于打字效果)
  private isTyping = false;                             // 是否正在打字
  private currentCharIndex = 0;                         // 当前打字索引
  private reconnectAttempts = 0;                        // 重连尝试次数
  private readonly MAX_RECONNECT_ATTEMPTS = 3;          // 最大重连次数
  private readonly RECONNECT_INTERVAL = 3000;           // 重连间隔(毫秒)
  private readonly TYPING_INTERVAL = 50;                // 打字间隔(毫秒)
  
  // 私有构造函数,防止外部直接实例化
  private constructor() {}

  /**
   * 获取WebSocketManager实例
   * 单例模式实现
   */
  static getInstance(): WebSocketManager {
    if (!WebSocketManager.instance) {
      WebSocketManager.instance = new WebSocketManager();
    }
    return WebSocketManager.instance;
  }

  /**
   * 获取消息显示元素
   */
  private getMessageElement(): HTMLElement | null {
    return document.getElementById("msgContent");
  }

  /**
   * 清空消息内容
   */
  private clearMessageContent(): void {
    const element = this.getMessageElement();
    if (element) {
      element.innerText = '';
    }
  }

  /**
   * 处理WebSocket接收到的消息
   * @param event WebSocket消息事件
   */
  private handleWebSocketMessage(event: MessageEvent): void {
    try {
      const data = JSON.parse(event.data) as WebSocketMessage;
      
      // 如果收到结束消息,关闭连接
      if (data.code === 'messageEnd') {
        this.closeConnection();
        return;
      }

      // 处理成功消息
      if (data.isSuccess) {
        this.messageQueue.push(data.data);
        this.processMessageQueue();
      } else {
        this.closeConnection();
      }
    } catch (error) {
      console.error('处理WebSocket消息时出错:', error);
      this.closeConnection();
    }
  }

  /**
   * 处理消息队列
   * 确保消息按顺序显示
   */
  private processMessageQueue(): void {
    if (!this.isTyping && this.messageQueue.length > 0) {
      this.isTyping = true;
      const message = this.messageQueue.shift();
      if (message) {
        this.charQueue = message.split('');
        this.writeNextChar();
      }
    }
  }

  /**
   * 实现打字机效果
   * 逐字显示消息内容
   */
  private writeNextChar(): void {
    const element = this.getMessageElement();
    if (!element) return;

    if (this.currentCharIndex < this.charQueue.length) {
      element.textContent += this.charQueue[this.currentCharIndex];
      this.currentCharIndex++;
      setTimeout(() => this.writeNextChar(), this.TYPING_INTERVAL);
    } else {
      this.isTyping = false;
      this.charQueue = [];
      this.currentCharIndex = 0;
      this.processMessageQueue();
    }
  }

  /**
   * 设置WebSocket事件监听器
   */
  private setupWebSocketListeners(): void {
    if (!this.socket) return;

    // 连接建立时的处理
    this.socket.onopen = () => {
      console.log('WebSocket连接已建立');
      this.reconnectAttempts = 0;
    };

    // 接收消息的处理
    this.socket.onmessage = (event) => {
      this.handleWebSocketMessage(event);
    };

    // 错误处理
    this.socket.onerror = (error) => {
      console.error('WebSocket错误:', error);
      this.attemptReconnect();
    };

    // 连接关闭的处理
    this.socket.onclose = () => {
      console.log('WebSocket连接已关闭');
      this.attemptReconnect();
    };
  }

  /**
   * 尝试重新连接
   * 在连接断开时自动重连
   */
  private attemptReconnect(): void {
    if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
      this.reconnectAttempts++;
      console.log(`尝试重新连接... 第${this.reconnectAttempts}`);
      setTimeout(() => {
        this.connect();
      }, this.RECONNECT_INTERVAL);
    } else {
      console.error('达到最大重连次数');
    }
  }

  /**
   * 建立WebSocket连接
   * @param requestParameters 可选的请求参数
   */
  connect(requestParameters?: RequestParameters): void {
    try {
      this.socket = new WebSocket('ws://localhost:3000/');
      this.setupWebSocketListeners();
      
      // 如果有请求参数,在连接建立后发送
      if (requestParameters) {
        this.socket.addEventListener('open', () => {
          this.socket?.send(JSON.stringify(requestParameters));
          this.clearMessageContent();
        });
      }
    } catch (error) {
      console.error('建立WebSocket连接时出错:', error);
    }
  }

  /**
   * 关闭WebSocket连接
   */
  closeConnection(): void {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.close();
      this.socket = null;
      console.log('WebSocket连接已关闭');
    }
  }
}

/**
 * 创建WebSocket连接的工具函数
 * @param requestParameters 请求参数
 */
export const createWebSocketConnection = (requestParameters: RequestParameters): void => {
  const wsManager = WebSocketManager.getInstance();
  wsManager.connect(requestParameters);
};
注意事项
  • 交互注意,如果当前已经存在链接,就避免用户触发二次链接,这样会导致AI的回答无法被正确的输出和控制
以上代码仅供思路参考,请根据实际业务进行处理!

http://www.kler.cn/a/593886.html

相关文章:

  • 实现一个函数,将驼峰命名法的字符串转换为下划线命名法。
  • 剑指 Offer II 112. 最长递增路径
  • Java的表达式自动类型提升
  • Java操作RabbitMQ
  • 基于ArcGIS和ETOPO-2022 DEM数据分层绘制全球海陆分布
  • VLLM专题(三十一)—架构概述
  • 蓝桥杯十四届C++B组真题题解
  • 计算机网络基础:网络配置与管理
  • springboot实现文件上传到服务器上,并通过url访问
  • 批量将 PPT 转换为PDF/XPS/JPG图片等其它格式
  • 谈谈 CSS 中z - index属性的作用及在什么情况下会失效。
  • LVGL和其他图形库区别于联系
  • 1.环境搭建VUE+Spring boot
  • 「清华大学、北京大学」DeepSeek 课件PPT专栏
  • 小型状态机实现
  • Kubeasz工具快速部署K8Sv1.27版本集群(二进制方式)
  • Promethues 添加访问密码
  • 数据结构与算法的学习路线
  • Redis设置开机自启报错start-limit-hit
  • MySQL配置主从复制教程(MySQL8)