/** * AI Realtime Transcriber State Management * @module AITranscriberState * @description Manages realtime voice transcription state and operations, including message reception and history */ import { ITUIStore } from '../interface/ITUIStore'; import { StoreName, NAME } from '../const/index'; import TuiStore from '../TUIStore/tuiStore'; // AI Transcriber events export enum RealtimeTranscriberEvent { onReceiveTranscriberMessage = 'onReceiveTranscriberMessage', onRealtimeTranscriberStarted = 'onRealtimeTranscriberStarted', onRealtimeTranscriberStopped = 'onRealtimeTranscriberStopped', onRealtimeTranscriberError = 'onRealtimeTranscriberError', } // Transcriber language type export type TranscriberLanguage = string; // Transcriber message interface export interface TranscriberMessage { segmentId: string; speakerUserId: string; sourceText: string; translationTexts?: Map; timestamp: number; isCompleted: boolean; } // Event callback types export interface RealtimeTranscriberEventInfoMap { [RealtimeTranscriberEvent.onReceiveTranscriberMessage]: { roomId: string | number; message: TranscriberMessage; }; [RealtimeTranscriberEvent.onRealtimeTranscriberStarted]: { roomId: string | number; transcriberRobotId: string; sourceLanguage: string; }; [RealtimeTranscriberEvent.onRealtimeTranscriberStopped]: { roomId: string | number; transcriberRobotId: string; }; [RealtimeTranscriberEvent.onRealtimeTranscriberError]: { roomId: string | number; transcriberRobotId: string; error: number; errorMessage: string; }; } export type RealtimeTranscriberEventCallback = ( eventInfo: RealtimeTranscriberEventInfoMap[T] ) => void; const TUIStore: ITUIStore = TuiStore.getInstance(); export default class AIAssistant { static instance: AIAssistant; private _callService: any; private eventListeners = new Map(); constructor(options: { callService: any }) { this._callService = options.callService; } static getInstance(options: { callService: any }) { if (!AIAssistant.instance) { AIAssistant.instance = new AIAssistant(options); } return AIAssistant.instance; } /** * Get AI Transcriber Manager instance * @returns AITranscriberManager instance or null if not available */ private getTranscriberManager = () => { const callEngine = this._callService?.getTUICallEngineInstance(); if (!callEngine) { console.warn(`${NAME.PREFIX}Failed to get call engine instance.`); return null; } const trtcCloud = callEngine.getTRTCCloudInstance?.(); if (!trtcCloud || !trtcCloud.getAITranscriberManager) { console.warn(`${NAME.PREFIX}AI Transcriber not supported.`); return null; } return trtcCloud.getAITranscriberManager(); }; private triggerEvent = ( event: T, eventInfo: RealtimeTranscriberEventInfoMap[T], ): void => { const listeners = this.eventListeners.get(event); if (listeners) { listeners.forEach((callback) => { try { callback(eventInfo); } catch (error) { console.error(`${NAME.PREFIX}Error in event callback for ${event}:`, error); } }); } }; public startRealtimeTranscriber = async (config: { sourceLanguage: TranscriberLanguage; translationLanguages?: TranscriberLanguage[]; }) => { const transcriberManager = this.getTranscriberManager(); if (!transcriberManager) { return null; } try { const transcriberRobotId = await transcriberManager.startRealtimeTranscriber(config); TUIStore.update(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID, transcriberRobotId); TUIStore.update(StoreName.CALL, NAME.IS_AI_TRANSCRIBER_RUNNING, true); return transcriberRobotId; } catch (error) { console.error(`${NAME.PREFIX}startRealtimeTranscriber error:`, error); throw error; } }; public stopRealtimeTranscriber = async () => { const transcriberManager = this.getTranscriberManager(); if (!transcriberManager) { return; } try { const transcriberRobotId = TUIStore.getData(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID); if (transcriberRobotId) { await transcriberManager.stopRealtimeTranscriber(transcriberRobotId); } TUIStore.update(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID, null); TUIStore.update(StoreName.CALL, NAME.IS_AI_TRANSCRIBER_RUNNING, false); } catch (error) { console.error(`${NAME.PREFIX}stopRealtimeTranscriber error:`, error); throw error; } }; public updateRealTimeTranscriber = async (config: { sourceLanguage: TranscriberLanguage; translationLanguages?: TranscriberLanguage[]; }) => { const transcriberManager = this.getTranscriberManager(); if (!transcriberManager) { return null; } try { const currentTranscriberRobotId = TUIStore.getData(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID); if (currentTranscriberRobotId) { await transcriberManager.stopRealtimeTranscriber(currentTranscriberRobotId); } const newTranscriberRobotId = await transcriberManager.startRealtimeTranscriber(config); TUIStore.update(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID, newTranscriberRobotId); return newTranscriberRobotId; } catch (error) { console.error(`${NAME.PREFIX}updateRealTimeTranscriber error:`, error); throw error; } }; public subscribeEvent = (event: RealtimeTranscriberEvent, callback: RealtimeTranscriberEventCallback) => { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, []); } this.eventListeners.get(event)?.push(callback); }; public unsubscribeEvent = (event: RealtimeTranscriberEvent, callback: RealtimeTranscriberEventCallback) => { const listeners = this.eventListeners.get(event); if (listeners) { const index = listeners.indexOf(callback); if (index > -1) { listeners.splice(index, 1); } if (listeners.length === 0) { this.eventListeners.delete(event); } } }; private onReceiveTranscriberMessage = ( _roomId: string | number, message: { segmentId: string; speakerUserId: string; sourceText: string; translationTexts?: Map; timestamp: number; isCompleted: boolean; }, ) => { const { segmentId } = message; let realtimeMessageList: TranscriberMessage[] = TUIStore.getData(StoreName.CALL, NAME.REALTIME_MESSAGE_LIST) || []; const idx = realtimeMessageList.findIndex(msg => msg.segmentId === segmentId); if (idx >= 0) { realtimeMessageList[idx] = message; } else { realtimeMessageList.push(message); } TUIStore.update(StoreName.CALL, NAME.REALTIME_MESSAGE_LIST, [...realtimeMessageList]); this.triggerEvent(RealtimeTranscriberEvent.onReceiveTranscriberMessage, { roomId: _roomId, message, }); }; private onRealtimeTranscriberStarted = (_roomId: string | number, _transcriberRobotId: string, _sourceLanguage: string) => { this.triggerEvent(RealtimeTranscriberEvent.onRealtimeTranscriberStarted, { roomId: _roomId, transcriberRobotId: _transcriberRobotId, sourceLanguage: _sourceLanguage, }); }; private onRealtimeTranscriberStopped = (_roomId: string | number, _transcriberRobotId: string) => { this.triggerEvent(RealtimeTranscriberEvent.onRealtimeTranscriberStopped, { roomId: _roomId, transcriberRobotId: _transcriberRobotId, }); }; private onRealtimeTranscriberError = (_roomId: string | number, _transcriberRobotId: string, error: number, errorMessage: string) => { this.triggerEvent(RealtimeTranscriberEvent.onRealtimeTranscriberError, { roomId: _roomId, transcriberRobotId: _transcriberRobotId, error, errorMessage, }); }; public bindEvent = () => { const transcriberManager = this.getTranscriberManager(); if (!transcriberManager) { return; } try { transcriberManager.addListener({ onReceiveTranscriberMessage: this.onReceiveTranscriberMessage, onRealtimeTranscriberStarted: this.onRealtimeTranscriberStarted, onRealtimeTranscriberStopped: this.onRealtimeTranscriberStopped, onRealtimeTranscriberError: this.onRealtimeTranscriberError, }); } catch (error) { console.error(`${NAME.PREFIX}bindEvent error:`, error); } }; public unbindEvent = () => { const transcriberManager = this.getTranscriberManager(); if (!transcriberManager) { return; } try { transcriberManager.removeListener({ onReceiveTranscriberMessage: this.onReceiveTranscriberMessage, onRealtimeTranscriberStarted: this.onRealtimeTranscriberStarted, onRealtimeTranscriberStopped: this.onRealtimeTranscriberStopped, onRealtimeTranscriberError: this.onRealtimeTranscriberError, }); } catch (error) { console.error(`${NAME.PREFIX}unbindEvent error:`, error); } }; public reset = () => { TUIStore.update(StoreName.CALL, NAME.TRANSCRIBER_ROBOT_ID, null); TUIStore.update(StoreName.CALL, NAME.REALTIME_MESSAGE_LIST, []); TUIStore.update(StoreName.CALL, NAME.IS_AI_TRANSCRIBER_ENABLED, false); this.eventListeners.clear(); }; }