Is it possible to disable the user typing input text area in the V4 bot framework in any channel ? I have this as part of customer requirement Can someone please help me
The box you refer to is called the send box. If you are using BotFramework-Web Chat, you can disable it by passing the value via styleOptions like so:
<script>
(async function () {
const styleOptions = {
hideSendBox = true
}
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={directLine},
styleOptions={styleOptions}
/>,
document.getElementById( 'webchat' )
);
})
</script>
If you are using the iFrame embedded version of Web Chat, it is not configurable.
Hope of help!
Edit
If you are wanting the send box to be responsive according to the type of activity received from the bot, then you will want to use a combination of the activityMiddleware() function as well as an event emitter/listener. In the following example, I am hiding/showing the send box when suggestedActions is an activity property.
Please be aware that the data values should be "none" and "flex". In particular, the latter value when it is not suggestedActions in order to maintain the current code.
<script>
(async function () {
[...]
const activityMiddleware = () => next => card => {
const { activity: { suggestedActions } } = card;
const toggleSendBoxEvent = new Event('ToggleSendBoxEvent')
if (suggestedActions) {
toggleSendBoxEvent.data = "none";
window.dispatchEvent(toggleSendBoxEvent);
} else {
toggleSendBoxEvent.data = "flex";
window.dispatchEvent(toggleSendBoxEvent);
}
return next(card);
)
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={ window.WebChat.createDirectLine({ token }) }
activityMiddleware={ activityMiddleware }
/>,
document.getElementById( 'webchat' )
);
window.addEventListener('ToggleSendBoxEvent', ( { data } ) => {
const sendBoxes = document.getElementsByClassName("main");
let send_Box;
for (let sendBox of sendBoxes) {
send_Box = sendBox;
}
send_Box.setAttribute('style', `display:${ data }`)
})
});
</script>
Hope of help!
Related
Is there a guide on how to create my own Web Chat (I don't want to be using/customizing the one that is in react). I would like to make it from scratch using Blazor or Angular, but I haven't found a good guide on how to do that.
No guides that I am aware of.
It also depends on what you mean. If you are wanting to build the tool that would implement something like Web Chat, then I'd recommend installing different tools to use while referencing their respective SDKs.
BotFramework-WebChat is impressive but it is also quite sophisticated in its build and features.
Botkit's Web Client, while presently deprecated, would serve as a different perspective to tackling the same problem.
Botkit's botbuilder-adapter-web offers a different method by communicating via webhooks and websocket with a site.
Regardless, you will need to have a method for capturing the incoming messages as well as posting messages back. This means you will need to utilize the BotFramework-DirectLineJS SDK which is what BotFramework-WebChat relies on. You will then need to consider how you will want to handle the different activity types and messages that could be sent by the bot or user. For instance, how to handle:
Messages:
Simple text messages
Messages with attachments: cards, adaptive cards, images, audio/speech, etc.
Events
ConversationUpdates, including membersAdded
Traces
Typing
There are accessibility considerations, as well, for the hearing- and sight-impaired, etc., plus a host of other things to think of.
Here is a custom chat I created some time ago. It does the absolute basic and is not pretty. In it I call a local token server I run for getting a valid Directline token.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<title>Custom Chat Using Direct Line</title>
<script crossorigin="anonymous" src="https://unpkg.com/#babel/standalone#7.8.7/babel.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-7aeOL7r9BM1QyTIsoLYJYNsRLfVnQCtLmwgXlxteDNhJf0xUGj9PKP8F5w2Fx92/"
src="https://unpkg.com/botframework-directlinejs#0.11.6/dist/directline.js"></script>
</head>
<body>
<h2>Custom Chat Using Direct Line</h2>
<div class="input-container">
<input type="text" class="input-box" name="input-box" value="Hi">
<button type="button" class="input-button">Send</button>
</div>
<div class="response-container">
</div>
<script type="text/babel" data-presets="es2015,react,stage-3">
( async function () {
const { ConnectionStatus, DirectLine } = window.DirectLine;
const renderStatus = {
DISPLAY_ACTIVITIES: 'display',
HIDE_ACTIVITIES: 'hide',
RESET_VIEW: 'reset',
MAINTAIN_VIEW: 'maintain'
}
let previousWatermark = 0;
let currentWatermark = 0;
let displayStatus = renderStatus.DISPLAY_ACTIVITIES;
let viewStatus = renderStatus.MAINTAIN_VIEW;
let responseHistory;
// Custom 'token' server retrieves a Direct Line token
// Server stores the Direct Line secret exchanging it for a token when requested
const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
const { token } = await res.json();
var directLine = new DirectLine( {
token: token
} )
// Function posts activity to Direct Line, when called
const postActivity = ( dl, text ) => {
dl.postActivity(
{
from: { id: 'abc123', name: 'JohnDoe' }, // required (from.name is optional)
type: 'message',
text: `${ text }`
}
)
// As all message activities are logged below, there is no need to log the posted activity
.subscribe(
id => id,
error => console.log( "Error posting activity", error )
);
}
// Posts user message on button click
const inputButton = document.querySelector( '.input-button' );
const inputBox = document.querySelector( '.input-box' );
inputButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
const text = inputBox.value;
postActivity( directLine, text );
} );
inputBox.onkeyup = ( e ) => {
const keyCode = e ? ( e.which ? e.which : e.keyCode ) : event.keyCode;
if ( keyCode === 13 ) {
const text = inputBox.value;
postActivity( directLine, text );
}
};
// Updates UI with all response activity
let responseContainer = document.querySelector( '.response-container' );
const subscribeToActivities = (dl) => {
dl.activity$
.filter( activity => {
return activity.type === 'message';
} )
.subscribe(
activity => {
const text = activity.text;
if (!activity.attachments) {
const id = activity.from.id;
currentWatermark = Number(dl.watermark);
if ( viewStatus === renderStatus.RESET_VIEW && currentWatermark <= previousWatermark && responseHistory.length > 0) {
displayStatus = renderStatus.HIDE_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
}
else if ( displayStatus === renderStatus.DISPLAY_ACTIVITIES && currentWatermark >= previousWatermark ) {
switch ( id ) {
case 'botberg':
responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: ${ text } </li></ul>`;
displayStatus = renderStatus.HIDE_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
break;
}
}
else if ( displayStatus === renderStatus.HIDE_ACTIVITIES && currentWatermark >= previousWatermark ) {
switch ( id ) {
case 'botberg':
break;
default:
responseContainer.innerHTML += `<ul class="chat-list"><li>From User: ${ text } </li></ul>`;
displayStatus = renderStatus.DISPLAY_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
}
}
}
else {
responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: Client received unsuppported attachment type </li></ul>`;
}
}
);
}
subscribeToActivities(directLine);
directLine.connectionStatus$
.subscribe( async connectionStatus => {
switch ( connectionStatus ) {
case ConnectionStatus.Uninitialized:
console.log( 'CONNECTION_STATUS => UNINITIALIZED ', directLine );
break;
case ConnectionStatus.Connecting:
console.log( 'CONNECTION_STATUS => CONNECTING ', directLine );
break;
case ConnectionStatus.Online:
console.log( 'CONNECTION_STATUS => ONLINE ', directLine );
break;
case ConnectionStatus.ExpiredToken:
console.log( 'CONNECTION_STATUS => EXPIRED TOKEN ', directLine );
break;
case ConnectionStatus.FailedToConnect:
console.log( 'CONNECTION_STATUS => FAILED TO CONNECT ', directLine );
break;
case ConnectionStatus.Ended:
console.log( 'CONNECTION_STATUS => ENDED ', directLine );
break;
}
} );
} )()
</script>
</body>
</html>
What I'm trying to achieve is that I want a user to be able to submit a registration only when they follow the correct guidelines, such as having a minimum of 8 characters, etc. I have a button set up with a Link router:
//CSS
import "./Register.css"
//LOCAL LIBRARIES
import { VoidFunc, ChangeEventHandlerType,
FormEventHandlerType } from "./index"
import useSubmit from "../../Hooks/Register/useSubmit";
//EXTERNAL LIBRARIES
import { Link } from "react-router-dom";
const Register = () => {
{...}
//Manage Admin
const [username, password, confirmPassword, handleChange,
handleSubmit, placeholder, error, registerUser] = useSubmit()
return (
<div className="div-register">
{...}
<form className="form-register"
onSubmit={handleSubmit as FormEventHandlerType}>
{...}
<Link className="link-register" to={registerUser as string}>
<button className="btn-submit-register">Submit</button>
</Link>
{...}
</form>
</div>
);
};
export default Register;
My attempt was to use a useState hook to manage the url destination of the component so that when the input fields are not filled in properly, the Links "to" property is set to "/register" (keeping the user on the same page they are already on), and when they are, it is set to "/admin" which is the main logged in destination.
I have another .tsx file where I refactored a few elements into one arrow function. The bits I cut out just had conditions like "If the user didnt fill out a field, add an error text to the placeholder for that input field":
//LOCAL
import RegisterUser from "../../Components/Register/RegisterUser";
//EXTERNAL
import { useState } from "react";
import {iState, StateType,
ChangeEventType, FormEventType} from
"../../Pages/Register/index"
const useSubmit = () =>{
//Number Check
let hasNumber = /\d/;
//Error
const [error, setError] = useState<string>("")
//Is Password valid
const [registerUser, setRegisterUser] = useState<string>("/register")
//Placeholder
const [placeholder, setPlaceholder] = useState<string>("")
//Manage Admin
const [state, setState] = useState<StateType>(iState)
const {username, password, confirmPassword} = state
const handleChange = (event: ChangeEventType) => {
//unfinished code
event.preventDefault()
const {name, value} = event.target
setState({...state, [name]: value})
console.log(state)
}
const handleSubmit = (event: FormEventType) => {
event.preventDefault()
{...FAILURE CONDITIONS}
//SUCCESS BLOCK
else {
setPlaceholder("")
setError("")
setRegisterUser("/admin")
}
}
return [username, password,
confirmPassword, handleChange, handleSubmit, placeholder,
error, registerUser]
}
export default useSubmit;
Any help is appreciated!
It seems you are really just wanting to navigate to "/admin" from the submit handler. For this you should use an imperative navigation instead of the Link. (1) the Link's click handler won't wait for the form element's onSubmit handler to complete, and (2) it certainly can't wait for the registerUser state to update.
Use the useHistory hook to access the history object if you are using react-router-dom#5 or the useNavigate hook to access the navigate function if you are using react-router-dom#6.
useSubmit
Import and use either useHistory or useNavigate hooks, depending on installed version, and invoke in the submit handler on the success logic branch. Remove the registerUser state. Return an object instead of an array so destructuring assignment doesn't depend on array indices.
import { useHistory } from 'react-router-dom'; // RRDv5
import { useNavigate } from 'react-router-dom'; // RRDv6
const useSubmit = () => {
const history = useHistory(); // RRDv5
const navigate = useNavigate(); // RRDv6
// Number Check
let hasNumber = /\d/;
// Error
const [error, setError] = useState<string>("");
// Placeholder
const [placeholder, setPlaceholder] = useState<string>("");
// Manage Admin
const [state, setState] = useState<StateType>(iState);
const {username, password, confirmPassword} = state;
const handleChange = (event: ChangeEventType) => {
// unfinished code
event.preventDefault()
const { name, value } = event.target;
setState(state => ({
...state,
[name]: value
}));
}
const handleSubmit = (event: FormEventType) => {
event.preventDefault()
{...FAILURE CONDITIONS}
// SUCCESS BLOCK
else {
history.replace("/admin"); // RRDv5
navigate("/admin", { replace: true }); // RRDv6
}
}
return {
username,
password,
confirmPassword,
handleChange,
handleSubmit,
placeholder,
error,
};
};
Register
Remove the Link component wrapping the submit button, unnecessary. Use object destructuring assignment from the useSubmit hook.
const Register = () => {
...
// Manage Admin
const {
username,
password,
confirmPassword,
handleChange,
handleSubmit,
placeholder,
error,
} = useSubmit();
return (
<div className="div-register">
...
<form
className="form-register"
onSubmit={handleSubmit as FormEventHandlerType}
>
...
<button className="btn-submit-register">Submit</button>
...
</form>
</div>
);
};
I think I came up with a solution. Instead of:
<Link className="link-register" to={"/admin"}>
<button className="btn-submit-register">Submit</button>
</Link>
I just added the following to my success block in useSubmit:
window.location.href="/admin"
I am trying to add a search field for an address using google's address autocomplete in a Stenciljs component. There aren't any resources on it.
First you'll need to load the google maps api script, so that you can interact with the global google.maps object. You can either do that by including a script tag, or write something like the following helper function.
const googleApiKey = '...';
export const importMapsApi = async () =>
new Promise<typeof google.maps>((resolve, reject) => {
if ('google' in window) {
return resolve(google.maps);
}
const script = document.createElement('script');
script.onload = () => resolve(google.maps);
script.onerror = reject;
script.src = `https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&libraries=places`;
document.body.appendChild(script);
});
In order to get the TypeScript types for the global google object, you should install #types/googlemaps into your dev-dependencies.
Then you'll need to implement a function that allows you to search for places, e. g.:
export const searchForPlaces = async (input: string, sessionToken: google.maps.places.AutocompleteSessionToken) => {
const maps = await importMapsApi();
const service = new maps.places.AutocompleteService();
return new Promise<google.maps.places.AutocompletePrediction[]>((resolve) =>
service.getPlacePredictions({ input, sessionToken }, (predictions, status) => {
if (status !== maps.places.PlacesServiceStatus.OK) {
return resolve([]);
}
resolve(predictions);
}),
);
};
None of this is specific to Stencil btw. All that is left to do is to use the searchForPlaces function in your component. A very simple example would be something like:
#Component({ tag: 'maps-place-search' })
export class MapsPlaceSearch {
sessionToken: string;
#State()
predictions: google.maps.places.AutocompletePrediction[];
async componentWillLoad() {
const maps = await importMapsApi();
this.sessionToken = new maps.places.AutoCompleteSessionToken();
}
async search = (e: InputEvent) => {
const searchTerm = e.target.value;
if (!searchTerm) {
this.predictions = [];
return;
}
this.predictions = await searchForPlaces(searchTerm, this.sessionToken);
}
render() {
return (
<Fragment>
<input onInput={this.search} />
<ul>
{this.predictions.map(prediction => <li key={prediction.description}>{prediction.description}</li>)}
</ul>
<Fragment>
);
}
}
The place search will give you a placeId for each prediction. That and the session token you can pass on to a maps.places.PlacesService to get the details for the place and auto-fill your form or whatever you're trying to achieve.
I cannt find a way to pass parameters to bot framework when a session begins.
I want to pass a projectId when user starts a chat and it must be passed automatically (ie the user must not be prompted for this information).
I'm trying in vain dozens of approaches since a few days and nothing works.
I have 2 approaches that are closed to the result:
var projectId ="191";
var d1 = window.WebChat.createDirectLine({ token });
const store = window.WebChat.createStore(
{},
function() {
return function(next) {
return function(action) {
if (action.type === 'DIRECT_LINE/POST_ACTIVITY') {
action = window.simpleUpdateIn(
action,
['payload', 'activity', 'channelData'],
() => ({
'email': "testemail1#test.com",
'projectId': projectId
})
)
}
return next(action);
}
}
);
window.WebChat.renderWebChat({
directLine: d1,
store: store,
styleOptions:styleOptions
}, document.getElementById('webchat'));
var user = {
id: 'default-user',
name: 'user name'
};
var activity = {
from: user,
name: 'startConversation',
type: 'event',
value: '',
channelData: { "email": "test2#b.com","usertoken": userToken,"projectId":projectId}
};
d1.postActivity(activity).subscribe(function(id) {
if (console) {
console.log('"trigger requestWelcomeDialog" sent');
}
});
If i try to pass parameters through the store and DIRECT_LINE/POST_ACTIVITY, the parameter are passed after the session created (too late for my need)
2.If I passe through the postActivity of type event, I manage to override the OnEventAsynxc and access the parameters. But then I don't understand how to pass them to the dialogs.
In the dialogs, when I dump the channelData and conversationstate or userstate, it's empty.
Please help, my need is simply to automatically pass a projectID when a chat is initiated.
Looks trivial but it is not.
protected override async Task OnEventAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
IStatePropertyAccessor<OnboardingState> accessor = UserState.CreateProperty<OnboardingState>(nameof(OnboardingState));
OnboardingState state = await accessor.GetAsync(turnContext, () => new OnboardingState());
state.Ticket = new CIWTicket();
state.Ticket.SourceEmailAddress = email;
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
You can achieve this by using dispatch() to send an event with the data stored in the payload. dispatch() is called and the event sent when direct line finalizes its connection to the bot ("CONNECTION_FULFILLED"), as demonstrated below.
<script>
(async function () {
const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => async action => {
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
console.log('Event dispatched');
dispatch( {
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'SEND_PROJECT_ID',
value: 191
}
} )
}
return next(action);
});
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={ window.WebChat.createDirectLine({ token }) }
store={store}
/>,
document.getElementById( 'webchat' );
)
})
</script>
Developer's console
Logged by bot
Hope of help!
I'm currently migrating some React Client Side Rendering to Server Side Rendering but I can't figure out how to call a function, nothing happens once an element is triggered in the view.
I defined some functions in my component but once I click on the trigger, it's like the click has been removed once rendered.
match({ history, routes, location: req.url }, (err, redirectLocation, renderProps) => {
if (err) {
return res.status(500).send(err.message);
}
if (redirectLocation) {
return res.redirect(302, redirectLocation.pathname + redirectLocation.search);
}
if (!renderProps) {
return res.status(404).send('Not found');
}
const app = (
<ApolloProvider client={apolloClient} >
<RouterContext {...renderProps}/>
</ApolloProvider>
);
renderToStringWithData(app).then((content) => {
const initialState = {[apolloClient.reduxRootKey]: apolloClient.getInitialState() };
const html = <Html content={content} state={initialState} />;
res.status(200);
res.send(`<!doctype html>\n${ReactDOMServer.renderToStaticMarkup(html)}`);
res.end();
});