flare / flare-ui /src /app /services /websocket.service.ts
ciyidogan's picture
Update flare-ui/src/app/services/websocket.service.ts
fe6235b verified
raw
history blame
5.87 kB
import { Injectable } from '@angular/core';
import { Subject, Observable, timer } from 'rxjs';
import { retry, tap } from 'rxjs/operators';
export interface WebSocketMessage {
type: string;
[key: string]: any;
}
export interface TranscriptionResult {
text: string;
is_final: boolean;
confidence: number;
}
export interface StateChangeMessage {
from: string;
to: string;
}
@Injectable({
providedIn: 'root'
})
export class WebSocketService {
private socket: WebSocket | null = null;
private url: string = '';
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private reconnectDelay = 1000;
// Subjects for different message types
private messageSubject = new Subject<WebSocketMessage>();
private transcriptionSubject = new Subject<TranscriptionResult>();
private stateChangeSubject = new Subject<StateChangeMessage>();
private errorSubject = new Subject<string>();
private connectionSubject = new Subject<boolean>();
// Public observables
public message$ = this.messageSubject.asObservable();
public transcription$ = this.transcriptionSubject.asObservable();
public stateChange$ = this.stateChangeSubject.asObservable();
public error$ = this.errorSubject.asObservable();
public connection$ = this.connectionSubject.asObservable();
constructor() {}
connect(sessionId: string): Promise<void> {
return new Promise((resolve, reject) => {
try {
// Construct WebSocket URL
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
this.url = `${protocol}//${host}/ws/conversation/${sessionId}`;
console.log(`🔌 Connecting to WebSocket: ${this.url}`);
this.socket = new WebSocket(this.url);
this.socket.onopen = () => {
console.log('✅ WebSocket connected');
this.reconnectAttempts = 0;
this.connectionSubject.next(true);
// Start keep-alive ping
this.startKeepAlive();
resolve();
};
this.socket.onmessage = (event) => {
try {
const message: WebSocketMessage = JSON.parse(event.data);
this.handleMessage(message);
} catch (error) {
console.error('Failed to parse WebSocket message:', error);
}
};
this.socket.onerror = (error) => {
console.error('❌ WebSocket error:', error);
this.errorSubject.next('WebSocket bağlantı hatası');
reject(error);
};
this.socket.onclose = () => {
console.log('🔌 WebSocket disconnected');
this.connectionSubject.next(false);
this.stopKeepAlive();
// Attempt reconnection
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.attemptReconnect(sessionId);
}
};
} catch (error) {
console.error('Failed to create WebSocket:', error);
reject(error);
}
});
}
disconnect(): void {
this.stopKeepAlive();
if (this.socket) {
this.socket.close();
this.socket = null;
}
this.connectionSubject.next(false);
}
send(message: WebSocketMessage): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(message));
} else {
console.warn('WebSocket is not connected');
this.errorSubject.next('WebSocket bağlantısı yok');
}
}
sendAudioChunk(audioData: string): void {
this.send({
type: 'audio_chunk',
data: audioData,
timestamp: Date.now()
});
}
sendControl(action: string, config?: any): void {
this.send({
type: 'control',
action: action,
config: config
});
}
private handleMessage(message: WebSocketMessage): void {
// Emit to general message stream
this.messageSubject.next(message);
// Handle specific message types
switch (message.type) {
case 'transcription':
this.transcriptionSubject.next({
text: message['text'],
is_final: message['is_final'],
confidence: message['confidence']
});
break;
case 'state_change':
this.stateChangeSubject.next({
from: message['from'],
to: message['to']
});
break;
case 'error':
this.errorSubject.next(message['message']);
break;
case 'tts_audio':
// Handle TTS audio chunks
this.messageSubject.next(message);
break;
case 'assistant_response':
// Handle assistant text response
this.messageSubject.next(message);
break;
}
}
private attemptReconnect(sessionId: string): void {
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`🔄 Attempting reconnection ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${delay}ms`);
setTimeout(() => {
this.connect(sessionId).catch(error => {
console.error('Reconnection failed:', error);
});
}, delay);
}
// Keep-alive mechanism
private keepAliveInterval: any;
private startKeepAlive(): void {
this.keepAliveInterval = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.send({ type: 'ping' });
}
}, 30000); // Ping every 30 seconds
}
private stopKeepAlive(): void {
if (this.keepAliveInterval) {
clearInterval(this.keepAliveInterval);
this.keepAliveInterval = null;
}
}
isConnected(): boolean {
return this.socket !== null && this.socket.readyState === WebSocket.OPEN;
}
}