@@ -11,19 +11,53 @@ import type {
1111} from '@js/ui/button' ;
1212import type Button from '@js/ui/button' ;
1313import type { Attachment } from '@js/ui/chat' ;
14- import type { UploadedEvent , UploadStartedEvent , ValueChangedEvent } from '@js/ui/file_uploader' ;
14+ import type {
15+ UploadedEvent ,
16+ UploadStartedEvent ,
17+ ValueChangedEvent ,
18+ } from '@js/ui/file_uploader' ;
19+ import type dxSpeechToText from '@js/ui/speech_to_text' ;
20+ import type {
21+ EndEvent ,
22+ InitializedEvent as InitializedSTTEvent ,
23+ Properties as SpeechToTextProperties ,
24+ ResultEvent ,
25+ StartClickEvent ,
26+ } from '@js/ui/speech_to_text' ;
1527import { current , isMaterial } from '@js/ui/themes' ;
1628import type { Item as ToolbarItem } from '@js/ui/toolbar' ;
1729import Toolbar from '@js/ui/toolbar' ;
1830import type { OptionChanged } from '@ts/core/widget/types' ;
1931import type { SupportedKeys } from '@ts/core/widget/widget' ;
2032import Widget from '@ts/core/widget/widget' ;
2133import FileUploader from '@ts/ui/file_uploader/file_uploader' ;
22- import type { CancelButtonClickEvent , FileValidationErrorEvent , Properties as FileUploaderProperties } from '@ts/ui/file_uploader/file_uploader.types' ;
34+ import type {
35+ CancelButtonClickEvent ,
36+ FileValidationErrorEvent ,
37+ Properties as FileUploaderProperties ,
38+ } from '@ts/ui/file_uploader/file_uploader.types' ;
2339import Informer from '@ts/ui/informer/informer' ;
2440import type { TextAreaProperties } from '@ts/ui/m_text_area' ;
2541import TextArea from '@ts/ui/m_text_area' ;
2642
43+ type EnterKeyEvent = NativeEventInfo < ChatTextArea , KeyboardEvent > ;
44+
45+ export type SendEvent = ClickEvent | EnterKeyEvent ;
46+
47+ type FileToSend = Attachment & {
48+ readyToSend : boolean ;
49+ } ;
50+
51+ export type Properties = TextAreaProperties & {
52+ fileUploaderOptions ?: FileUploaderProperties ;
53+
54+ speechToTextEnabled ?: boolean ;
55+
56+ speechToTextOptions ?: SpeechToTextProperties ;
57+
58+ onSend ?: ( e : SendEvent ) => void ;
59+ } ;
60+
2761const CHAT_TEXT_AREA_ATTACHMENTS = 'dx-chat-textarea-attachments' ;
2862export const CHAT_TEXT_AREA_ATTACH_BUTTON = 'dx-chat-textarea-attach-button' ;
2963
@@ -38,23 +72,15 @@ const ERRORS = {
3872 fileLimit : messageLocalization . format ( 'dxChat-fileLimitReachedWarning' , MAX_ATTACHMENTS_COUNT ) ,
3973} ;
4074
41- const isMobile = ( ) : boolean => devices . current ( ) . deviceType !== 'desktop' ;
42-
43- export const DEFAULT_ALLOWED_FILE_EXTENSIONS = [ '.jpg' , '.jpeg' , '.png' , '.gif' , '.webp' , '.bmp' , '.pdf' , '.docx' , '.xlsx' , '.pptx' , '.txt' , '.rtf' , '.csv' , '.md' ] ;
44-
45- type EnterKeyEvent = NativeEventInfo < ChatTextArea , KeyboardEvent > ;
46-
47- export type SendEvent = ClickEvent | EnterKeyEvent ;
48-
49- type FileToSend = Attachment & {
50- readyToSend : boolean ;
75+ const STT_INITIAL_STATE = {
76+ stylingMode : 'text' as const ,
77+ type : 'normal' ,
78+ stopIcon : 'micfilled' ,
5179} ;
5280
53- export type Properties = TextAreaProperties & {
54- fileUploaderOptions ?: FileUploaderProperties ;
81+ const isMobile = ( ) : boolean => devices . current ( ) . deviceType !== 'desktop' ;
5582
56- onSend ?: ( e : SendEvent ) => void ;
57- } ;
83+ export const DEFAULT_ALLOWED_FILE_EXTENSIONS = [ '.jpg' , '.jpeg' , '.png' , '.gif' , '.webp' , '.bmp' , '.pdf' , '.docx' , '.xlsx' , '.pptx' , '.txt' , '.rtf' , '.csv' , '.md' ] ;
5884
5985class ChatTextArea extends TextArea < Properties > {
6086 _informerTimeoutId ?: ReturnType < typeof setTimeout > | undefined ;
@@ -71,10 +97,14 @@ class ChatTextArea extends TextArea<Properties> {
7197
7298 _filesToSend ?: Map < File , FileToSend > ;
7399
100+ _initialInputText ?: string ;
101+
74102 _attachButton ?: Button ;
75103
76104 _sendButton ?: Button ;
77105
106+ _speechToTextButton ?: dxSpeechToText ;
107+
78108 _sendAction ?: ( e : Partial < SendEvent > ) => void ;
79109
80110 getAttachments ( ) : Attachment [ ] | undefined {
@@ -90,11 +120,13 @@ class ChatTextArea extends TextArea<Properties> {
90120 _getDefaultOptions ( ) : Properties {
91121 return {
92122 ...super . _getDefaultOptions ( ) ,
93- stylingMode : 'outlined' ,
94- placeholder : messageLocalization . format ( 'dxChat-textareaPlaceholder' ) ,
95123 autoResizeEnabled : true ,
96- valueChangeEvent : 'input' ,
97124 maxHeight : '53.86em' ,
125+ placeholder : messageLocalization . format ( 'dxChat-textareaPlaceholder' ) ,
126+ speechToTextEnabled : false ,
127+ speechToTextOptions : undefined ,
128+ stylingMode : 'outlined' ,
129+ valueChangeEvent : 'input' ,
98130 } ;
99131 }
100132
@@ -136,6 +168,8 @@ class ChatTextArea extends TextArea<Properties> {
136168 }
137169
138170 _init ( ) : void {
171+ this . _initialInputText = '' ;
172+
139173 super . _init ( ) ;
140174
141175 this . _createSendAction ( ) ;
@@ -208,16 +242,20 @@ class ChatTextArea extends TextArea<Properties> {
208242 }
209243
210244 _getToolbarItems ( ) : ToolbarItem [ ] {
211- const { fileUploaderOptions } = this . option ( ) ;
245+ const { fileUploaderOptions, speechToTextEnabled } = this . option ( ) ;
212246
213- const items = [
214- this . _getSendButtonConfig ( ) ,
215- ] ;
247+ const items : ToolbarItem [ ] = [ ] ;
216248
217249 if ( fileUploaderOptions ) {
218250 items . push ( this . _getAttachButtonConfig ( ) ) ;
219251 }
220252
253+ if ( speechToTextEnabled ) {
254+ items . push ( this . _getSpeechToTextButtonConfig ( ) ) ;
255+ }
256+
257+ items . push ( this . _getSendButtonConfig ( ) ) ;
258+
221259 return items ;
222260 }
223261
@@ -247,6 +285,54 @@ class ChatTextArea extends TextArea<Properties> {
247285 return configuration ;
248286 }
249287
288+ _getSpeechToTextButtonOptions ( ) : SpeechToTextProperties {
289+ const {
290+ activeStateEnabled,
291+ focusStateEnabled,
292+ hoverStateEnabled,
293+ speechToTextOptions,
294+ } = this . option ( ) ;
295+
296+ const options = {
297+ activeStateEnabled,
298+ focusStateEnabled,
299+ hoverStateEnabled,
300+ ...speechToTextOptions ,
301+ ...STT_INITIAL_STATE ,
302+ onEnd : ( e : EndEvent ) : void => {
303+ this . _initialInputText = '' ;
304+
305+ speechToTextOptions ?. onEnd ?.( e ) ;
306+ } ,
307+ onInitialized : ( e : InitializedSTTEvent ) : void => {
308+ this . _speechToTextButton = e . component ;
309+
310+ speechToTextOptions ?. onInitialized ?.( e ) ;
311+ } ,
312+ onResult : ( e : ResultEvent ) : void => this . _resultHandler ( e ) ,
313+ onStartClick : ( e : StartClickEvent ) : void => {
314+ const { text } = this . option ( ) ;
315+
316+ this . _initialInputText = text ;
317+
318+ speechToTextOptions ?. onStartClick ?.( e ) ;
319+ } ,
320+ } ;
321+
322+ return options ;
323+ }
324+
325+ _getSpeechToTextButtonConfig ( ) : ToolbarItem {
326+ // @ts -expect-error dxSpeechToText should be added to ToolbarItemComponent
327+ const configuration = {
328+ widget : 'dxSpeechToText' ,
329+ location : 'after' ,
330+ options : this . _getSpeechToTextButtonOptions ( ) ,
331+ } as ToolbarItem ;
332+
333+ return configuration ;
334+ }
335+
250336 _getSendButtonConfig ( ) : ToolbarItem {
251337 const {
252338 activeStateEnabled,
@@ -280,6 +366,22 @@ class ChatTextArea extends TextArea<Properties> {
280366 return configuration ;
281367 }
282368
369+ _resultHandler ( e : ResultEvent ) : void {
370+ const { speechToTextOptions } = this . option ( ) ;
371+
372+ // @ts -expect-error SpeechRecognition API is not supported in TS
373+ const speechRecognitionResult = Object . values ( e . event . results )
374+ // @ts -expect-error SpeechRecognition API is not supported in TS
375+ . map ( ( resultItem ) => ( resultItem [ 0 ] . transcript as string ) . trim ( ) )
376+ . join ( ' ' ) ;
377+
378+ const result = `${ this . _initialInputText } ${ speechRecognitionResult } ` . trim ( ) ;
379+
380+ this . option ( { value : result } ) ;
381+
382+ speechToTextOptions ?. onResult ?.( e ) ;
383+ }
384+
283385 _initFileUploader ( ) : void {
284386 const { fileUploaderOptions } = this . option ( ) ;
285387
@@ -449,6 +551,7 @@ class ChatTextArea extends TextArea<Properties> {
449551 case 'focusStateEnabled' :
450552 case 'hoverStateEnabled' :
451553 this . _sendButton ?. option ( name , value ) ;
554+ this . _speechToTextButton ?. option ( name , value ) ;
452555 break ;
453556
454557 case 'text' :
@@ -464,6 +567,14 @@ class ChatTextArea extends TextArea<Properties> {
464567 this . _handleFileUploaderOptionsChange ( args ) ;
465568 break ;
466569
570+ case 'speechToTextEnabled' :
571+ this . _toolbar ?. option ( { items : this . _getToolbarItems ( ) } ) ;
572+ break ;
573+
574+ case 'speechToTextOptions' :
575+ this . _speechToTextButton ?. option ( this . _getSpeechToTextButtonOptions ( ) ) ;
576+ break ;
577+
467578 default :
468579 super . _optionChanged ( args ) ;
469580 }
0 commit comments