Create custom Web Chat for Microsoft Bot Framework - botframework

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>

Related

next js with ssr enabled: Og image not displaying while sharing using linkedln

I am forming og image(s3 link) based on id send as query params.
In app.js, inside head, I am assigning og:image content value to s3 link based on router path.
const { query } = useRouter();
const { id } = query;
<Head>
{router.pathname === "/certificate/course-complete" &&
<meta
property="og:image"
content={`${process.env.CERTIFICATE_S3_ORIGIN}/course-complete/${id}.jpg`}
/>
}
</Head>
Now, when i inspect the page, i look for og:image meta, its present and its valus is s3 url. But, its not read by https://www.linkedin.com/post-inspector/ or https://socialsharepreview.com/. they are not getting those og:image from the url.
If i hard code the s3 link, then linkedln inspector is getting the og:image and its working fine.
<Head>
<meta
property="og:image"
content={`${process.env.CERTIFICATE_S3_ORIGIN}/course-complete/199111.jpg`}
/>
</Head>
Please help how can i generate dynamic og:image(s3 link formed using id as query params from the url) for a page in next js
getInitialProps used in _document.js
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
in netlify, the following is used to run the application.
next build and next export
code to set meta tags
import { Col, Row } from 'antd';
import { NextSeo } from 'next-seo';
export async function getServerSideProps(context) {
const { id } = context.query;
return {
props: {id: id}
}
}
const Certificate = (props) => {
return (
<>
<NextSeo
canonical={`${process.env.CERTIFICATE_URL}/?id=${props.id}`}
openGraph={{
url: `${process.env.CERTIFICATE_S3_ORIGIN}/`,
images: [
{
url: `${process.env.CERTIFICATE_S3_ORIGIN}/${props.id}.jpg`,
width: 800,
height: 600,
type: 'image/jpeg',
}
],
}}
/>
// code here
</>
);
};
export default Certificate;

Google address autocomplete api in Stenciljs

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.

Disable user typing text box c#

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!

Get meeting link with Google calendar api

I'm looking to use the Google calendar API for an application, but I'd like to get a Google meet link (for a G Suite account). As far as I can see that option is not possible, or am I wrong?
Thanks in advance!
As per https://cloud.google.com/blog/products/application-development/hangouts-meet-now-available-in-google
You need to search for element with entryPointType = "video" in ConferenceData.entryPoints[] and then you can use matchedObject.uri to get the Google meet link.
In Javascript / nodejs the solution will be something similar to:
var {google} = require('googleapis');
const eventId = "<yourEventId>";
const calendarId = "<yourCalendarId>";
const calendar = google.calendar({
version : "v3",
auth : auth
});
calendar.events.get({
calendarId,
eventId
}, ( err, res ) => {
if( err ) {
console.log( err );
} else {
const conferenceData = res.conferenceData;
if( conferenceData ) {
const entryPoints = conferenceData.entryPoints;
if( entryPoints ) {
const videoDetails = entryPoints.find( entryPoint => entryPoint.entryPointType == "video" );
if( videoDetails ) {
console.log( "Google Meet link", videoDetails.uri );
} else {
console.log( "No video details available" );
}
} else {
console.log( "No entry points available" );
}
} else {
console.log( "No conference data available" );
}
}
});
As of now, there's only hangoutLink in calendar API. If you'd like to know how to use it in Javascript, check this guide.

Handling UI Event using Apollo Stack SSR

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();
});

Resources