Skip to content

Commit 6a5cd66

Browse files
committed
the chat can speak
1 parent 518c68c commit 6a5cd66

File tree

5 files changed

+1756
-1705
lines changed

5 files changed

+1756
-1705
lines changed

lib/ChatBot.jsx

Lines changed: 87 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from './components';
1919
import Recognition from './recognition';
2020
import { ChatIcon, CloseIcon, SubmitIcon, MicIcon } from './icons';
21-
import { isMobile } from './utils';
21+
import { isMobile, speakFn } from './utils';
2222

2323
class ChatBot extends Component {
2424
/* istanbul ignore next */
@@ -39,7 +39,7 @@ class ChatBot extends Component {
3939
recognitionEnable: props.recognitionEnable && Recognition.isSupported(),
4040
defaultUserSettings: {},
4141
};
42-
42+
this.speak = speakFn(props.speechSynthesis);
4343
this.renderStep = this.renderStep.bind(this);
4444
this.getTriggeredStep = this.getTriggeredStep.bind(this);
4545
this.generateRenderedStepsById = this.generateRenderedStepsById.bind(this);
@@ -95,24 +95,22 @@ class ChatBot extends Component {
9595
steps[firstStep.id].message = firstStep.message;
9696
}
9797

98-
const {
99-
currentStep,
100-
previousStep,
101-
previousSteps,
102-
renderedSteps,
103-
} = storage.getData({
104-
cacheName,
105-
cache,
106-
firstStep,
107-
steps,
108-
}, () => {
109-
// focus input if last step cached is a user step
110-
this.setState({ disabled: false }, () => {
111-
if (enableMobileAutoFocus || !isMobile()) {
112-
this.input.focus();
113-
}
114-
});
115-
});
98+
const { currentStep, previousStep, previousSteps, renderedSteps } = storage.getData(
99+
{
100+
cacheName,
101+
cache,
102+
firstStep,
103+
steps,
104+
},
105+
() => {
106+
// focus input if last step cached is a user step
107+
this.setState({ disabled: false }, () => {
108+
if (enableMobileAutoFocus || !isMobile()) {
109+
this.input.focus();
110+
}
111+
});
112+
},
113+
);
116114

117115
this.setState({
118116
currentStep,
@@ -210,12 +208,7 @@ class ChatBot extends Component {
210208

211209
triggerNextStep(data) {
212210
const { enableMobileAutoFocus } = this.props;
213-
const {
214-
defaultUserSettings,
215-
previousSteps,
216-
renderedSteps,
217-
steps,
218-
} = this.state;
211+
const { defaultUserSettings, previousSteps, renderedSteps, steps } = this.state;
219212
let { currentStep, previousStep } = this.state;
220213
const isEnd = currentStep.end;
221214

@@ -406,15 +399,18 @@ class ChatBot extends Component {
406399
renderedSteps.push(currentStep);
407400
previousSteps.push(currentStep);
408401

409-
this.setState({
410-
currentStep,
411-
renderedSteps,
412-
previousSteps,
413-
disabled: true,
414-
inputValue: '',
415-
}, () => {
416-
this.input.blur();
417-
});
402+
this.setState(
403+
{
404+
currentStep,
405+
renderedSteps,
406+
previousSteps,
407+
disabled: true,
408+
inputValue: '',
409+
},
410+
() => {
411+
this.input.blur();
412+
},
413+
);
418414
}
419415
}
420416

@@ -425,23 +421,29 @@ class ChatBot extends Component {
425421
const value = inputValue;
426422

427423
if (typeof result !== 'boolean' || !result) {
428-
this.setState({
429-
inputValue: result.toString(),
430-
inputInvalid: true,
431-
disabled: true,
432-
}, () => {
433-
setTimeout(() => {
434-
this.setState({
435-
inputValue: value,
436-
inputInvalid: false,
437-
disabled: false,
438-
}, () => {
439-
if (enableMobileAutoFocus || !isMobile()) {
440-
this.input.focus();
441-
}
442-
});
443-
}, 2000);
444-
});
424+
this.setState(
425+
{
426+
inputValue: result.toString(),
427+
inputInvalid: true,
428+
disabled: true,
429+
},
430+
() => {
431+
setTimeout(() => {
432+
this.setState(
433+
{
434+
inputValue: value,
435+
inputInvalid: false,
436+
disabled: false,
437+
},
438+
() => {
439+
if (enableMobileAutoFocus || !isMobile()) {
440+
this.input.focus();
441+
}
442+
},
443+
);
444+
}, 2000);
445+
},
446+
);
445447

446448
return true;
447449
}
@@ -466,6 +468,7 @@ class ChatBot extends Component {
466468
customStyle,
467469
hideBotAvatar,
468470
hideUserAvatar,
471+
speechSynthesis,
469472
} = this.props;
470473
const { options, component, asMessage } = step;
471474
const steps = this.generateRenderedStepsById();
@@ -475,10 +478,12 @@ class ChatBot extends Component {
475478
return (
476479
<CustomStep
477480
key={index}
481+
speak={this.speak}
478482
step={step}
479483
steps={steps}
480484
style={customStyle}
481485
previousStep={previousStep}
486+
previousValue={previousStep.value}
482487
triggerNextStep={this.triggerNextStep}
483488
/>
484489
);
@@ -489,6 +494,8 @@ class ChatBot extends Component {
489494
<OptionsStep
490495
key={index}
491496
step={step}
497+
speak={this.speak}
498+
previousValue={previousStep.value}
492499
triggerNextStep={this.triggerNextStep}
493500
bubbleOptionStyle={bubbleOptionStyle}
494501
/>
@@ -500,13 +507,15 @@ class ChatBot extends Component {
500507
key={index}
501508
step={step}
502509
steps={steps}
510+
speak={this.speak}
503511
previousStep={previousStep}
504512
previousValue={previousStep.value}
505513
triggerNextStep={this.triggerNextStep}
506514
avatarStyle={avatarStyle}
507515
bubbleStyle={bubbleStyle}
508516
hideBotAvatar={hideBotAvatar}
509517
hideUserAvatar={hideUserAvatar}
518+
speechSynthesis={speechSynthesis}
510519
isFirst={this.isFirstPosition(step)}
511520
isLast={this.isLastPosition(step)}
512521
/>
@@ -558,8 +567,9 @@ class ChatBot extends Component {
558567
const icon =
559568
(_.isEmpty(inputValue) || speaking) && recognitionEnable ? <MicIcon /> : <SubmitIcon />;
560569

561-
const inputPlaceholder = speaking ? recognitionPlaceholder :
562-
currentStep.placeholder || placeholder;
570+
const inputPlaceholder = speaking
571+
? recognitionPlaceholder
572+
: currentStep.placeholder || placeholder;
563573

564574
const inputAttributesOverride = currentStep.inputAttributes || inputAttributes;
565575

@@ -613,18 +623,19 @@ class ChatBot extends Component {
613623
{...inputAttributesOverride}
614624
/>
615625
)}
616-
{!currentStep.hideInput && !hideSubmitButton && (
617-
<SubmitButton
618-
className="rsc-submit-button"
619-
style={submitButtonStyle}
620-
onClick={this.handleSubmitButton}
621-
invalid={inputInvalid}
622-
disabled={disabled}
623-
speaking={speaking}
624-
>
625-
{icon}
626-
</SubmitButton>
627-
)}
626+
{!currentStep.hideInput &&
627+
!hideSubmitButton && (
628+
<SubmitButton
629+
className="rsc-submit-button"
630+
style={submitButtonStyle}
631+
onClick={this.handleSubmitButton}
632+
invalid={inputInvalid}
633+
disabled={disabled}
634+
speaking={speaking}
635+
>
636+
{icon}
637+
</SubmitButton>
638+
)}
628639
</Footer>
629640
</ChatBotContainer>
630641
</div>
@@ -670,6 +681,11 @@ ChatBot.propTypes = {
670681
userDelay: PropTypes.number,
671682
width: PropTypes.string,
672683
height: PropTypes.string,
684+
speechSynthesis: PropTypes.shape({
685+
enable: PropTypes.bool,
686+
lang: PropTypes.string,
687+
voice: PropTypes.instanceOf(window.SpeechSynthesisVoice),
688+
}),
673689
};
674690

675691
ChatBot.defaultProps = {
@@ -701,6 +717,11 @@ ChatBot.defaultProps = {
701717
recognitionEnable: false,
702718
recognitionLang: 'en',
703719
recognitionPlaceholder: 'Listening ...',
720+
speechSynthesis: {
721+
enable: false,
722+
lang: 'en',
723+
voice: null,
724+
},
704725
style: {},
705726
submitButtonStyle: {},
706727
toggleFloating: undefined,

lib/steps_components/custom/CustomStep.jsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import _ from 'lodash';
12
import React, { Component } from 'react';
23
import PropTypes from 'prop-types';
34
import Loading from '../common/Loading';
@@ -16,14 +17,15 @@ class CustomStep extends Component {
1617
}
1718

1819
componentDidMount() {
19-
const { step } = this.props;
20+
const { speak, step, previousValue } = this.props;
2021
const { delay, waitAction } = step;
2122

2223
setTimeout(() => {
2324
this.setState({ loading: false }, () => {
2425
if (!waitAction && !step.rendered) {
2526
this.props.triggerNextStep();
2627
}
28+
speak(step, previousValue);
2729
});
2830
}, delay);
2931
}
@@ -44,15 +46,8 @@ class CustomStep extends Component {
4446
const { style } = this.props;
4547

4648
return (
47-
<CustomStepContainer
48-
className="rsc-cs"
49-
style={style}
50-
>
51-
{
52-
loading ? (
53-
<Loading />
54-
) : this.renderComponent()
55-
}
49+
<CustomStepContainer className="rsc-cs" style={style}>
50+
{loading ? <Loading /> : this.renderComponent()}
5651
</CustomStepContainer>
5752
);
5853
}
@@ -64,6 +59,12 @@ CustomStep.propTypes = {
6459
style: PropTypes.object.isRequired,
6560
previousStep: PropTypes.object.isRequired,
6661
triggerNextStep: PropTypes.func.isRequired,
62+
previousValue: PropTypes.any,
63+
speak: PropTypes.func,
64+
};
65+
CustomStep.defaultProps = {
66+
previousValue: '',
67+
speak: _.noop,
6768
};
6869

6970
export default CustomStep;

0 commit comments

Comments
 (0)