microsoft / BotFramework-WebChat

A highly-customizable web-based client for Azure Bot Services.
https://www.botframework.com/
MIT License
1.58k stars 1.53k forks source link

webchat.js open links in same tab #5025

Closed jagjotkhera closed 4 months ago

jagjotkhera commented 7 months ago

Is it an issue related to Adaptive Cards?

No

Is this an accessibility issue?

No

What version of Web Chat are you using?

Latest production

Which distribution are you using Web Chat from?

Bundle (webchat.js)

Which hosting environment does this issue primarily affect?

SharePoint

Which browsers and platforms do the issue happened?

Browser: Edge (latest), Browser: Chrome (latest), Browser: Firefox (latest), Browser: Safari (latest), Platform: iOS/iPadOS, Platform: Android

Which area does this issue affect?

Suggested action

What is the public URL for the website?

https://yourroomuat.citc.health.nsw.gov.au/

Please describe the bug

We are using webchat.js with directline in our html file, the bot works however all the hyperlinks in the bot are coming up with target set to as "_blank" even internal url's as well as external. We wanted to check how we can change this behavior. We did try to fix it with the markdown.js file as described here however it doesn't fix the complete problem as we have suggested actions with link and those still have hyperlinks set as target blank. Is there a way to update these so that any internal link within the site comes as _self target and external link as _blank. Hyperlinks Issue

Version we are currently using '\n\n\n\n'

Do you see any errors in console log?

no

How to reproduce the issue?

open chat bot and type quit and click send image image

What do you expect?

Links to show however the internal links to open in the same tab and external links to open in a new tab. The site also has pdf and they should open in new tab

What actually happened?

All links open in new tab including internal, external and pdf as well. We need internal links to open in the same tab

Do you have any screenshots or recordings to repro the issue?

No response

Adaptive Card JSON

No response

Additional context

No response

jagjotkhera commented 7 months ago

We are following the below link for the bot development

OEvgeny commented 7 months ago

@jagjotkhera the link provided doesn't work to me. I'm getting:

504 Gateway Time-out

So I have to ask: is the issue appears with Suggeted action links or Adaptive card links? More details here: https://github.com/microsoft/BotFramework-WebChat/issues/3833#issuecomment-815145159

The progress on implementing a centralized way of handling links is tracked in #2892

jagjotkhera commented 7 months ago

@OEvgeny thanks for responding, I believe the site is only accessible from Australia which is why you are getting 504. To answer your question we are using List style hero card on a multichoice, please see below screenshot from composer image

Also we had a look at issue 2892 and implemented the markdown functionality and it works for the plain text (see below) MarkdownLinks

However when the link is part of the List style hero card on a multichoice the markdown doesn't work (see below) Hyperlinks Issue

Please let me know if you need any more information.

OEvgeny commented 7 months ago

@jagjotkhera got it, I'll look into it tomorrow. I think, it falls into the Adaptive Cards bucket and if they support adjusting action link targets, you should be able to create a different card with actions.

Alternatively you could try using cardActionMiddleware to adjust actions.

jagjotkhera commented 7 months ago

Thanks @OEvgeny , I'm not sure what you mean by cardActionMiddleware as in our webchat.js we are rendering the bot like this

window.WebChat.renderWebChat( { directLine: directLine, username: 'Web Chat User', locale: 'en-AU', renderMarkdown: text => MarkdownIt.render(text), // Passing 'styleSet' when rendering Web Chat styleSet, styleOptions: { hideUploadButton: true, autoScrollSnapOnPage: messSnap, autoScrollSnapOnPageOffset: -75, suggestedActionLayout: 'stacked', }, overrideLocalizedStrings: (strings, language) => ({ ...strings, TEXT_INPUT_PLACEHOLDER: 'Ask a Question ...' }), }, Can you please let me know where do i need to update

OEvgeny commented 7 months ago

You're still able to use the middleware to adjust the actions. See this example.

More about hero card here.

Unfortunatelly adaptive cards OpenUrl action doesn't provide a way to configure the link target. So it looks like the middleware is the only way to implement the functionality at the moment.

jagjotkhera commented 7 months ago

Thanks @OEvgeny , I did try to implement the middleware option however I don't think the code is being called. See below my render method function loadBotResources(bearerToken) { / ChatBot not to be loaded on pages in the games and tools site, if url contains 'games-and-tools', skip chatbot injection. /

if(_spPageContextInfo.webServerRelativeUrl.indexOf('games-and-tools') <0)
    {
        /* ChatBot Jquery page injection code Start */

        jQuery("document").ready(function(){
            jQuery("head").append('<style type="text/css" href="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/YourRoomChatBot.css"></style>')
            jQuery("head").append('<link id="ChatBotCSSLink" rel="stylesheet" type="text/css" href="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/YourRoomChatBot.css">')
            jQuery("head").append('<link rel="preconnect" href="https://fonts.googleapis.com">')
            jQuery("head").append('<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>')
            jQuery("head").append('<link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">')
            jQuery('#yourroomchatbot').html('<!-- Icon Expanded View Code --><div id="ChatBotIconBanner" role="banner"><div id="ChatBotBannerContent" role="group">' +
            '<div id="ChatBotIcon" role="banner" onclick="ShowChatBot()"><img src="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/Chatbot.png" ' +
            'title="YasBot Icon" alt="YasBot Icon"></img></div><h3 id="ChatBotBannerText" onclick="ShowChatBot()">Ask me questions ...</h3><img src="' + 
            _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/questions.png" title="Questions Icon" alt="Questions Icon"  onclick="ShowChatBot()"></im' +
            'g></div></div><!-- Icon Minimised View Code --><div id="ChatBotIconMin" role="banner"><img src="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Lib' +
            'rary/ChatBot/Chatbot.png" title="YasBot Icon Minimised" alt="YasBot Icon Minimised" onclick="ShowChatBot()"></img></div><!-- BotView Code --><div id="' +
            'chatBotIframe" role="main" onfocusout="hideChatbot"><div id="chatBotHeader" role="banner"><div id="ChatBotHeaderLogo" role="img"><img src="' + 
            _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/Chatbot.png" title="YASLogo" alt="Yaz ChatBot Logo"></img></div><div id="ChatBotHeaderTe' +
            'xt" role="heading"><h3>Talk to Yas</h3></div><div id=MinimiseChatBot role="button"><img src="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/C' +
            'hatBot/minimise.png" title="Minimise ChatBot" alt="Minimise ChatBot" onclick="ShowIconMin()"></img></div></div><div id="chatBotBody" role="main"></div><div id=' +
            '"chatBotFooter" role="contentinfo"><div id="FooterLinks" role="link"><p><a href="#" onclick="clickPrivacy()">Privacy information</a> | <a href="#" onclick="clickEmergency()">Emergency information</a></p' +
            '></div><div class="FooterImg" role="img"><img src="' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/FontSizeIcon.png" title="Font si' +
            'ze icon" onclick="fontToggle()" alt="Toggle font size"></img></div><div class="FooterImg" role="img"><img src="' + _spPageContextInfo.siteAbsoluteUrl + 
            '/Style%20Library/ChatBot/Phone.webp" onclick="clickPhone()" title="Phone Icon" alt="Support contacts"></img></div></div>')
            jQuery('#ChatBotIconM').hide();
            jQuery('#ChatBotIconBanner').hide();
            jQuery('#ChatBotIconMin').hide();
        });

        /* ChatBot jQuery page injection code Start */

        //Init fontSize Variable
        fontSize = false

        /* ChatBot Bot Framework config and render code Start */
        import('' + _spPageContextInfo.siteAbsoluteUrl + '/Style%20Library/ChatBot/webchat.js').then(WebChat => {
            (async function () {
                //create session to hold conversation token
                let { token, conversation_Id, timeStamp } = sessionStorage;
                if(!token || tokenExpired()) {
                        console.log("Attempting to retrieve a new token from 'https://directline.botframework.com//v3/directline/tokens/generate'");

                        const res = await fetch('https://directline.botframework.com//v3/directline/tokens/generate', { method: 'POST', headers: { Authorization :bearerToken}});
                        const { token: directLineToken, conversationId, expires_in } = await res.json();
                        //Save token to session

                        if(res.status == 200)
                        {
                            sessionStorage[ 'token' ] = directLineToken;
                            sessionStorage[ 'conversation_Id' ] = conversationId;
                            sessionStorage[ 'timeStamp' ] = Date.now() + expires_in * 1000;
                            token = directLineToken;
                        }
                }

                const directLine = createDirectLine( {
                    token
                });

                // "styleSet" is a set of CSS rules which are generated from "styleOptions"             
                const styleSet = window.WebChat.createStyleSet({
                    // BotWindow customisatons
                    rootHeight: '100%',
                    rootWidth: '100%',
                    backgroundColor: 'white',
                    showSpokenText: true,
                    videoHeight: 270, // based on bubbleMaxWidth, 480 / 16 * 9 = 270
                    paddingRegular: 5,
                    paddingWide: 10,

                    // YASbot bubble customisations
                    botAvatarImage: '/Style%20Library/ChatBot/Chatbot.png',
                    botAvatarInitials: 'YAS',
                    bubbleBackground: '#D9D9D9',
                    bubbleBorder: 'solid 1px #444',
                    bubbleBorderRadius: 10,
                    bubblePadding: 5,
                    bubbleTextColor: 'black',

                    // User bubble custominsations
                    userAvatarImage: '/Style%20Library/ChatBot/questions.png',
                    userAvatarInitials: 'YU',
                    bubbleFromUserBackground: '#62CDC7',
                    bubbleFromUserBorder: 'solid 1px #444',
                    bubbleFromUserBorderRadius: 10,
                    bubbleFromUserTextColor: 'black',

                    // Sendbox customisations
                    hideSendBox: false,
                    hideUploadButton: 1,
                    microphoneButtonColorOnDictate: '#F33',
                    sendBoxBackground: 'white',
                    sendBoxButtonColor: '#62CDC7', // defaults to subtle
                    sendBoxButtonColorOnDisabled: '#CCC',
                    sendBoxButtonColorOnFocus: '#333',
                    sendBoxButtonColorOnHover: '#333',
                    sendBoxDisabledTextColor: '#CCC', // defaults to subtle
                    sendBoxHeight: 40,
                    sendBoxMaxHeight: 200,
                    sendBoxTextColor: '#818080',
                    sendBoxBorderBottom: '',
                    sendBoxBorderLeft: '',
                    sendBoxBorderRight: '',
                    sendBoxBorderTop: 'solid 1px #E6E6E6',
                    sendBoxPlaceholderColor: '#818080', // defaults to subtle
                    sendBoxTextWrap: true,
                    suggestedActionBorderRadius: 4,
                });

                // After generated, you can modify the CSS rules
                styleSet.textContent = {
                    ...styleSet.textContent,
                    fontFamily: "'Inter', 'Arial', sans-serif",
                    fontWeight: 'regular',
                    lineHeight: 1.25,
                };

                window.WebChat.renderWebChat(
                {
                    directLine: directLine,
                    username: 'Web Chat User',
                    locale: 'en-AU',

                    // We are adding a new middleware to handle card action
                    cardActionMiddleware: () => next => async ({ cardAction, getSignInUrl }) => {
                    console.log(cardAction)
                      const { type, value } = cardAction;

                      switch (type) {
                        case 'signin':
                        console.log("signin")
                          // For OAuth or sign-in popups, we will open the auth dialog directly.
                          const popup = window.open();
                          const url = await getSignInUrl();

                          popup.location.href = url;

                          break;

                        case 'openUrl':
                        console.log("openurl")
                          if (confirm(`Do you want to open this URL?\n\n${value}`)) {
                            window.open(value, '_blank');
                          }

                          break;

                        default:
                        console.log("default")

                          return next({ cardAction, getSignInUrl });
                      }
                    },
                    // Passing 'styleSet' when rendering Web Chat
                    styleSet,
                    styleOptions: { 
                        hideUploadButton: true,
                        autoScrollSnapOnPage: messSnap,
                        autoScrollSnapOnPageOffset: -75,
                        suggestedActionLayout: 'stacked',
                    },
                    overrideLocalizedStrings: (strings, language) => ({
                        ...strings,
                        TEXT_INPUT_PLACEHOLDER: 'Ask a Question ...'
                      }),
                },
                document.getElementById('chatBotBody')
                );
                document.querySelector('#chatBotBody> *').focus();

                /*  ChatBot Icon animation control, if panel is smaller than 600px
                    Flash the ChatBotIconMin, else run the full expand animation  */
                if (jQuery(window).width() > 767) {
                    expandBotIcon()
                } else {
                    flashBotIconMin()
                }
            })().catch(err => console.error(err));
        }).catch(err => console.error(err));    

        /* ChatBot Bot Framework config and render code Finished */
    }  

}

I have added console.log in the switch case but the code is not getting triggered. Can you please validate if this is what you were suggesting.

jagjotkhera commented 7 months ago

On further checking the code is getting called when the user clicks on any of the cards (multichoice suggested action) within the response and the card type is coming as "imBack", see below

image

OEvgeny commented 7 months ago

I was able to verify the middleware is running and is able to recieve the correct action along with values passed: image

      cardActionMiddleware: () => next => async ({ cardAction, getSignInUrl }) => {
        const { type, value } = cardAction;

        switch (type) {
          default:
            console.log(type, value)
            return next({ cardAction, getSignInUrl });
        }
      },

Attachment sent:

"attachments": [
            {
              "contentType": "application/vnd.microsoft.card.hero",
              "content": {
                "title": "Hero Card Actions",
                "buttons": [
                  {
                    "type": "imBack",
                    "title": "imBack",
                    "value": "string"
                  },
                  {
                    "type": "postBack",
                    "title": "postBack (string)",
                    "value": "string"
                  },
                  {
                    "type": "postBack",
                    "title": "postBack (JSON)",
                    "value": {
                      "value": "value"
                    }
                  },
                  {
                    "type": "messageBack",
                    "title": "messageBack (displayText + text + value)",
                    "text": "\"text\"",
                    "displayText": "displayText",
                    "value": {
                      "value": "value"
                    }
                  },
                  {
                    "type": "messageBack",
                    "title": "messageBack (displayText + text)",
                    "text": "\"text\"",
                    "displayText": "displayText"
                  },
                  {
                    "type": "messageBack",
                    "title": "messageBack (value)",
                    "value": {
                      "value": "value"
                    }
                  },
                  {
                    "type": "postBack",
                    "title": "postBack (empty)"
                  },
                  {
                    "type": "messageBack",
                    "title": "messageBack (empty)"
                  }
                ]
              }
            }
          ],

@jagjotkhera you should ignore actions you don't want to take control over, returning next(...). This is basically how middleware work

jagjotkhera commented 7 months ago

@OEvgeny , the attachment which you have sent to the webchat.js is not how we are sending data, we are sending it via the composer so dont have control over it snapshot of composer code

image

Also we want to capture this entire event as the links are in the text image

what do you recommend we change

OEvgeny commented 7 months ago

As far as I can tell, if you're using hero cards, you'll see actions with similary looking attachments comming your way through the directline connection.

Then you should be able to adjust the middleware on your end to respond to actions on your need. Logging every action as shown above, and inspecting the network, should help to figure out the rest of implementation details

jagjotkhera commented 7 months ago

@OEvgeny i was able to get the attachment via postman and below is the response image

Any thoughts how we can get this "Text" as part of the Middleware as this is where the hyperlinks are and we need to update the target.

Complete postman response PostManResponse.json

OEvgeny commented 7 months ago

@jagjotkhera from the payload, it looks like the hero card is not configured properly (see the action type for all actions is set to imBack instead of openUrl). And also the URL is missing.

Suggesting to look into Composer tutorials for an example of cards usage.

This is not a Composer support forum, the place is dedicated for WebChat issues only. Please use either StackOverflow or Composer Discussions if you have any further question regarding Composer

OEvgeny commented 4 months ago

Closing due to inactivity