Я использовал BotFramework SDK v4 (. NET) и WebChat v4 (React minizable-web-chat)
Контекст находится в ComponentDialog с WaterfallSteps:
- бот отправляет ChoicePrompt «Хотите, чтобы бот узнал ваше местоположение? ДА / НЕТ»
- Если ДА, бот отправляет событие «locationRequest»
- Веб-чат получает событие и показывает Кнопка GetLocation
- Когда пользователь нажимает на кнопку, он отправляет геолокации боту, и диалог продолжается
ComponentDialog:
public class FindNearestAgencyDialog : ComponentDialog
{
private string choice;
private string zipCode = string.Empty;
public FindNearestAgencyDialog(string id)
: base(id)
{
InitialDialogId = Id;
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
ZipCodeStepAsync,
LocationConfirmAsync,
ZipCodeConfirmAsync,
FindNearestAgencyAsync
};
AddDialog(new WaterfallDialog(Id, waterfallSteps));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
}
private static async Task<DialogTurnResult> ZipCodeStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(ChoicePrompt),
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Authoize bot to get your location?"),
Choices = new[] { new Choice { Value = "Decline" }, new Choice { Value = "Accept" } }.ToList()
});
}
private async Task<DialogTurnResult> LocationConfirmAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
choice = (stepContext.Result as FoundChoice)?.Value;
if (choice == "Decline")
{
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = MessageFactory.Text("What's your zip code ?") }, cancellationToken);
}
else
{
Activity activity = new Activity
{
Type = ActivityTypes.Event,
Name = "locationRequest"
};
await stepContext.Context.SendActivityAsync(activity, cancellationToken);
return await stepContext.NextAsync();
}
}
private async Task<DialogTurnResult> ZipCodeConfirmAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (choice == "Refuser")
{
zipCode = (string)stepContext.Result;
var msg = $"Je recherche l'agence la plus proche de votre code postal : {zipCode}.";
await stepContext.Context.SendActivityAsync(MessageFactory.Text(msg), cancellationToken);
}
return await stepContext.NextAsync();
}
private async Task<DialogTurnResult> FindNearestAgencyAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{ // DO SOEMTHING
// WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end.
return await stepContext.EndDialogAsync(stepContext.Result, cancellationToken: cancellationToken);
}
}
WebChat ( на основе React minizable-web-chat):
import React from 'react';
import { createStore, createStyleSet } from 'botframework-webchat';
import WebChat from './WebChat';
import './fabric-icons-inline.css';
import './MinimizableWebChat.css';
export default class extends React.Component{
constructor(props) {
super(props);
this.handleFetchToken = this.handleFetchToken.bind(this);
this.handleMaximizeButtonClick = this.handleMaximizeButtonClick.bind(this);
this.handleMinimizeButtonClick = this.handleMinimizeButtonClick.bind(this);
this.handleSwitchButtonClick = this.handleSwitchButtonClick.bind(this);
this.handleLocationButtonClick = this.handleLocationButtonClick.bind(this);
const store = createStore({}, ({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'webchat/join',
}
});
}
else if(action.type === 'DIRECT_LINE/INCOMING_ACTIVITY'){
if (action.payload.activity.name === 'locationRequest') {
this.setState(() => ({
locationRequested: true
}));
}
}
return next(action);
});
this.state = {
minimized: true,
newMessage: false,
locationRequested:false,
side: 'right',
store,
styleSet: createStyleSet({
backgroundColor: 'Transparent'
}),
token: 'token'
};
}
async handleFetchToken() {
if (!this.state.token) {
const res = await fetch('https://webchat-mockbot.azurewebsites.net/directline/token', { method: 'POST' });
const { token } = await res.json();
this.setState(() => ({ token }));
}
}
handleMaximizeButtonClick() {
this.setState(() => ({
minimized: false,
newMessage: false
}));
}
handleMinimizeButtonClick() {
this.setState(() => ({
minimized: true,
newMessage: false
}));
}
handleSwitchButtonClick() {
this.setState(({ side }) => ({
side: side === 'left' ? 'right' : 'left'
}));
}
handleLocationButtonClick(){
var x = document.getElementById("display");
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
this.setState(() => ({
locationRequested: false
}));
}
else {
x.innerHTML = "Geolocation API is not supported by this browser.";
}
function showPosition(position) {
x.innerHTML = "Latitude: " + position.coords.latitude + "<br>Longitude: " + position.coords.longitude;
}
}
render() {
const { state: {
minimized,
newMessage,
locationRequested,
side,
store,
styleSet,
token
} } = this;
return (
<div className="minimizable-web-chat">
{
minimized ?
<button
className="maximize"
onClick={ this.handleMaximizeButtonClick }
>
<span className={ token ? 'ms-Icon ms-Icon--MessageFill' : 'ms-Icon ms-Icon--Message' } />
{
newMessage &&
<span className="ms-Icon ms-Icon--CircleShapeSolid red-dot" />
}
</button>
:
<div
className={ side === 'left' ? 'chat-box left' : 'chat-box right' }
>
<header>
<div className="filler" />
<button
className="switch"
onClick={ this.handleSwitchButtonClick }
>
<span className="ms-Icon ms-Icon--Switch" />
</button>
<button
className="minimize"
onClick={ this.handleMinimizeButtonClick }
>
<span className="ms-Icon ms-Icon--ChromeMinimize" />
</button>
</header>
<WebChat
className="react-web-chat"
onFetchToken={ this.handleFetchToken }
store={ store }
styleSet={ styleSet }
token={ token }
/>
{
locationRequested ?
<div>
<p id="display"></p>
<button onClick={this.handleLocationButtonClick}>
Gélolocation
</button>
</div>
:
<div></div>
}
</div>
}
</div>
);
}
}
Как мы можем приостановить ComponentDialog, чтобы позволить пользователю нажать кнопку в веб-чате, отправить геолокацию боту, а затем возобновить ComponentDialog?