Skip to content

Commit

Permalink
Add SpeechRecognition
Browse files Browse the repository at this point in the history
  • Loading branch information
miladsoft committed Sep 25, 2024
1 parent 666c993 commit 4c1ba44
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,13 @@
<div *ngIf="showEmojiPicker" class="emoji-picker-container">
<emoji-mart (emojiClick)="addEmoji($event)" [darkMode]="darkMode"></emoji-mart>
</div>
<button class="ml-0.5" mat-icon-button>
<mat-icon [svgIcon]="'heroicons_outline:paper-clip'"></mat-icon>
<button mat-icon-button (click)="toggleSpeechRecognition()">
<mat-icon [ngClass]="{'active': isListening}" [svgIcon]="isListening ? 'heroicons_solid:microphone' : 'heroicons_outline:microphone'"></mat-icon>
</button>
</div>
<mat-form-field class="angor-mat-dense angor-mat-rounded angor-mat-bold ml-4 w-full"
subscriptSizing="dynamic">
<textarea class="max-h-20" matInput cdkTextareaAutosize #messageInput></textarea>
<textarea class="max-h-20" matInput cdkTextareaAutosize #messageInput (keydown)="handleKeyDown($event)"></textarea>
</mat-form-field>
<div class="my-px ml-4 flex h-11 items-center">
<button mat-icon-button (click)="sendMessage()">
Expand Down
208 changes: 121 additions & 87 deletions src/app/components/chat/conversation/conversation.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,54 +59,32 @@ export class ConversationComponent implements OnInit, OnDestroy {
private _unsubscribeAll: Subject<any> = new Subject<any>();
showEmojiPicker = false;
darkMode: boolean = false;
/**
* Constructor
*/
recognition: any;
finalTranscript: string = '';
isListening: boolean = false;
userEdited: boolean = false;


constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _chatService: ChatService,
private _angorMediaWatcherService: AngorMediaWatcherService,
private _ngZone: NgZone,
private _angorConfigService: AngorConfigService
) { }

// -----------------------------------------------------------------------------------------------------
// @ Decorated methods
// -----------------------------------------------------------------------------------------------------

/**
* Resize on 'input' and 'ngModelChange' events
*
* @private
*/
@HostListener('input')
@HostListener('ngModelChange')
private _resizeMessageInput(): void {
// This doesn't need to trigger Angular's change detection by itself
this._ngZone.runOutsideAngular(() => {
setTimeout(() => {
// Set the height to 'auto' so we can correctly read the scrollHeight
this.messageInput.nativeElement.style.height = 'auto';
) {
const SpeechRecognition = (window as any).SpeechRecognition || (window as any).webkitSpeechRecognition;

// Detect the changes so the height is applied
this._changeDetectorRef.detectChanges();

// Get the scrollHeight and subtract the vertical padding
this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`;

// Detect the changes one more time to apply the final height
this._changeDetectorRef.detectChanges();
});
});
if (!SpeechRecognition) {
console.error('Speech recognition is not supported in this browser.');
return;
}
this.recognition = new SpeechRecognition();
this.recognition.lang = 'en-US';
this.recognition.continuous = true;
this.recognition.interimResults = true;
this.setupRecognitionEvents();
}

// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------

/**
* On init
*/
ngOnInit(): void {
this._angorConfigService.config$.subscribe((config) => {
if (config.scheme === 'auto') {
Expand All @@ -115,86 +93,136 @@ export class ConversationComponent implements OnInit, OnDestroy {
this.darkMode = config.scheme === 'dark';
}
});
// Chat

this._chatService.chat$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((chat: Chat) => {
this.chat = chat;

// Mark for check

this._changeDetectorRef.markForCheck();
});

// Subscribe to media changes

this._angorMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Set the drawerMode if the given breakpoint is active

if (matchingAliases.includes('lg')) {
this.drawerMode = 'side';
} else {
this.drawerMode = 'over';
}

// Mark for check

this._changeDetectorRef.markForCheck();
});
}

/**
* On destroy
*/
@HostListener('input')
@HostListener('ngModelChange')
private _resizeMessageInput(): void {

this._ngZone.runOutsideAngular(() => {
setTimeout(() => {

this.messageInput.nativeElement.style.height = 'auto';


this._changeDetectorRef.detectChanges();


this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`;


this._changeDetectorRef.detectChanges();
});
});
}


setupRecognitionEvents(): void {
this.recognition.onresult = (event: any) => {
let interimTranscript = '';

for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
this.finalTranscript += transcript;
} else {
interimTranscript += transcript;
}
}


if (!this.userEdited) {
this.messageInput.nativeElement.value = this.finalTranscript + interimTranscript;
}
};

this.recognition.onerror = (event: any) => {
console.error('Speech recognition error detected: ', event.error);
};

this.recognition.onend = () => {
console.log('Speech recognition service disconnected');
this.isListening = false;
};
}


toggleSpeechRecognition(): void {
this.finalTranscript = '';
if (this.isListening) {
this.recognition.stop();
this.isListening = false;
} else {
this.recognition.start();
this.isListening = true;
this.userEdited = false;
}
}


handleUserInput(event: Event): void {
this.userEdited = true;
}



ngOnDestroy(): void {
// Unsubscribe from all subscriptions

this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();
}

// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------

/**
* Open the contact info
*/
openContactInfo(): void {
// Open the drawer

this.drawerOpened = true;

// Mark for check

this._changeDetectorRef.markForCheck();
}

/**
* Reset the chat
*/
resetChat(): void {
this._chatService.resetChat();

// Close the contact info in case it's opened

this.drawerOpened = false;

// Mark for check

this._changeDetectorRef.markForCheck();
}

/**
* Toggle mute notifications
*/
toggleMuteNotifications(): void {
// Toggle the muted

this.chat.muted = !this.chat.muted;

// Update the chat on the server

this._chatService.updateChat(this.chat.id, this.chat).subscribe();
}

/**
* Track by function for ngFor loops
*
* @param index
* @param item
*/
trackByFn(index: number, item: any): any {
return item.id || index;
}
Expand All @@ -209,28 +237,34 @@ export class ConversationComponent implements OnInit, OnDestroy {
});
}


handleKeyDown(event: KeyboardEvent): void {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.sendMessage();
}
}

sendMessage(): void {
const messageContent = this.messageInput.nativeElement.value.trim();

if (!messageContent) {
console.warn('Cannot send an empty message.');
return;
const message = this.messageInput.nativeElement.value;
if (message.trim()) {

this.messageInput.nativeElement.value = '';
this._chatService.sendPrivateMessage(messageContent)
.then(() => {
this.messageInput.nativeElement.value = '';
this.finalTranscript = '';
console.log('Message sent successfully.');
})
.catch((error) => {
console.error('Failed to send message:', error);
});
this.finalTranscript = '';
this.userEdited = false;
}

this._chatService.sendPrivateMessage(messageContent)
.then(() => {
this.messageInput.nativeElement.value = '';
console.log('Message sent successfully.');
})
.catch((error) => {
console.error('Failed to send message:', error);
});
}



addEmoji(event: any) {
this.messageInput.nativeElement.value += event.emoji.native;
this.showEmojiPicker = false;
Expand Down

0 comments on commit 4c1ba44

Please sign in to comment.