智能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的回答无法被正确的输出和控制