This commit is contained in:
2025-12-13 14:46:08 +08:00
parent e88f216ae2
commit 026600d27e

View File

@@ -0,0 +1,226 @@
/**
* WebSocket service for connecting to dialog engine (port 10002)
*/
export interface WebSocketMessage {
Topic?: string;
Data: {
Key: 'text' | 'audio' | 'question' | 'log';
Value?: string;
Text?: string;
IsFirst?: number;
IsEnd?: number;
};
Username?: string;
robot?: string;
}
export interface WebSocketServiceOptions {
url: string;
onMessage?: (message: WebSocketMessage) => void;
onConnect?: () => void;
onDisconnect?: () => void;
onError?: (error: Event) => void;
maxReconnectAttempts?: number;
}
const DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
const INITIAL_RECONNECT_DELAY = 1000;
const MAX_RECONNECT_DELAY = 30000;
export class WebSocketService {
private ws: WebSocket | null = null;
private url: string;
private reconnectAttempts = 0;
private maxReconnectAttempts: number;
private reconnectTimer: number | null = null;
private isConnecting = false;
private onMessageCallback?: (message: WebSocketMessage) => void;
private onConnectCallback?: () => void;
private onDisconnectCallback?: () => void;
private onErrorCallback?: (error: Event) => void;
constructor(options: WebSocketServiceOptions) {
this.url = options.url;
this.onMessageCallback = options.onMessage;
this.onConnectCallback = options.onConnect;
this.onDisconnectCallback = options.onDisconnect;
this.onErrorCallback = options.onError;
this.maxReconnectAttempts = options.maxReconnectAttempts || DEFAULT_MAX_RECONNECT_ATTEMPTS;
}
connect(): void {
if (this.isConnecting) {
console.log('[Fay] 已有连接正在进行中,跳过');
return;
}
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('[Fay] 达到最大重连次数限制');
return;
}
this.isConnecting = true;
console.log('[Fay] 正在连接到', this.url);
try {
this.cleanup(false);
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('[Fay] 已连接到数字人接口');
this.isConnecting = false;
this.reconnectAttempts = 0;
// 发送初始化消息
const initMsg = { Username: 'User', Output: false };
console.log('[Fay] 发送初始化消息:', initMsg);
this.send(initMsg);
this.onConnectCallback?.();
};
this.ws.onmessage = (event) => {
console.log('='.repeat(60));
console.log('[Fay] 收到消息:', event.data);
try {
const message: WebSocketMessage = JSON.parse(event.data);
console.log('[Fay] 解析后:', JSON.stringify(message, null, 2));
if (!message || !message.Data || !message.Data.Key) {
console.warn('[Fay] 消息格式不完整:', message);
return;
}
// 处理文本消息
if (message.Data.Key === 'text') {
const text = message.Data.Value || '';
const isFirst = message.Data.IsFirst === 1;
const isEnd = message.Data.IsEnd === 1;
if (text && text.trim()) {
console.log('[xmov] 收到文本消息:', text, { is_start: isFirst, is_end: isEnd });
this.onMessageCallback?.(message);
} else {
console.warn('[Fay] 收到空文本消息');
}
}
// 处理音频消息
else if (message.Data.Key === 'audio') {
const text = message.Data.Text || '';
const isFirst = message.Data.IsFirst === 1;
const isEnd = message.Data.IsEnd === 1;
if (text && text.trim()) {
console.log('[xmov] 收到音频消息:', text, { is_start: isFirst, is_end: isEnd });
this.onMessageCallback?.(message);
} else {
console.warn('[Fay] 收到空音频文本');
}
}
// 处理日志消息(思考中等状态提示)
else if (message.Data.Key === 'log') {
const logText = message.Data.Value || '';
console.log('[Fay] 收到日志消息:', logText);
this.onMessageCallback?.(message);
}
// 处理问题消息
else if (message.Data.Key === 'question') {
console.log('[Fay] 用户问题:', message.Data.Value);
} else {
console.log('[Fay] 未知消息类型:', message.Data.Key);
}
} catch (error) {
console.error('[Fay] 处理消息出错:', error, '原始数据:', event.data);
}
console.log('='.repeat(60));
};
this.ws.onerror = (error) => {
console.error('[Fay] WebSocket错误:', error);
this.isConnecting = false;
this.onErrorCallback?.(error);
};
this.ws.onclose = (event) => {
console.log('[Fay] WebSocket连接已关闭, 代码:', event.code, '原因:', event.reason);
this.isConnecting = false;
this.onDisconnectCallback?.();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
// 使用指数退避策略重连
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.calculateReconnectDelay();
console.log(`[Fay] ${delay}ms后尝试第${this.reconnectAttempts}次重连...`);
this.reconnectTimer = window.setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('[Fay] 已达到最大重连次数');
}
};
} catch (error) {
console.error('[Fay] 创建WebSocket连接失败:', error);
this.isConnecting = false;
}
}
send(data: any): void {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
try {
this.ws.send(JSON.stringify(data));
} catch (error) {
console.error('[Fay] 发送消息失败:', error);
}
}
}
disconnect(): void {
this.cleanup(true);
}
private cleanup(resetReconnectAttempts: boolean): void {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
try {
this.ws.onclose = null;
this.ws.onerror = null;
this.ws.onmessage = null;
this.ws.onopen = null;
this.ws.close();
} catch (error) {
console.warn('[Fay] 清理WebSocket时出错:', error);
}
this.ws = null;
}
this.isConnecting = false;
if (resetReconnectAttempts) {
this.reconnectAttempts = 0;
}
}
private calculateReconnectDelay(): number {
const exponentialDelay = Math.min(
INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts - 1),
MAX_RECONNECT_DELAY
);
const jitter = exponentialDelay * 0.2 * (Math.random() * 2 - 1);
return Math.floor(exponentialDelay + jitter);
}
isConnected(): boolean {
return this.ws?.readyState === WebSocket.OPEN;
}
}