Add File
This commit is contained in:
226
src/elderly/services/websocketService.ts
Normal file
226
src/elderly/services/websocketService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user