【问题标题】:Is there a way to persist chat history in MS Bot Framework Web Chat after a page reload/navigation to another page?页面重新加载/导航到另一个页面后,有没有办法在 MS Bot Framework 网络聊天中保留聊天历史记录?
【发布时间】:2019-11-19 23:32:29
【问题描述】:

我正在尝试在页面重新加载和导航到机器人链接到的网站上的其他页面期间保持用户与机器人的对话。

目前,这些操作会关闭机器人窗口并完全重新启动对话,从而再次触发机器人的欢迎消息。

有问题的机器人按照文档中的说明嵌入网页中:https://docs.microsoft.com/bs-latn-ba/azure/bot-service/bot-service-channel-connect-webchat?view=azure-bot-service-4.0

我已阅读其他文章,这些文章使用了 conversationId 来维护页面加载之间的聊天历史记录,尽管这是针对 DirectLine 频道的。以及其他一些建议将对话保存在数据库中并将消息传递回聊天窗口的其他文章。虽然这似乎不是最好的方法。

我尝试将 conversationId 传递到 iframe 中,但没有成功。有没有办法通过将 conversationId 传递到 iframe 来保持对话?

这是在 iframe 中显示聊天机器人的代码:

<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>

这是我尝试将 conversationId 作为参数传递:

<iframe src='https://webchat.botframework.com/embed/THECHATBOT?s=YOUR_SECRET_HERE&conversationId?=THE_CONVERSATIONID_VALUE' style='min-width: 400px; width: 100%; min-height: 500px;'></iframe>

我希望聊天窗口中填充用户之前的对话,我得到的是对话重置并且不保留任何历史记录。

【问题讨论】:

  • 据我所知,在使用 iframe 实现时这是不可能的。然而,基于BotFramework-Webchat 自己托管您的自定义 WebChat 相当容易。你能做到吗?

标签: azure botframework


【解决方案1】:

如果您希望进行任何类型的网络聊天自定义,那么我强烈建议您不要使用网络聊天频道 <iframe> 选项。如果您需要一个简单的插件组件,这很有用,但它提供的自定义选项数量远不及BotFramework-WebChat 提供的数量。

如果您将考虑使用基于 v4 react 的网络聊天产品(在上面的链接中引用),那么以下示例将为您提供您正在寻找的功能。

请注意,为简单起见,我将会话 ID 保存在会话存储中。

另外,我通过对本地运行的直线端点进行 API 调用来生成令牌。我在最后包含了代码来做同样的事情。您可以在 html 文件中针对 directline/tokens/generate 端点传递您的直接线路密码,但出于安全原因,强烈建议不要这样做。

最后,createDirectLine() 方法中使用的watermark 属性指定要显示的过去活动的数量(消息、卡片等)。

<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>WebChat</title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    html,
    body {
      height: 100%;
      width: 100%;
      margin: 0;
    }

    #webchat {
      height: 100%;
      width: 40%;
    }

    #webchat>* {
      height: 100%;
      width: 100%;
    }
  </style>
  </head>

  <body>
    <div id="webchat" role="main"></div>
    <script type="text/javascript"
      src="https://unpkg.com/markdown-it/dist/markdown-it.min.js"></script>
    <script
      src="https://cdn.botframework.com/botframework-webchat/master/webchat.js"></script>
    <script>
    ( async function () {
      let { token, conversationId } = sessionStorage;

      if (!token) {
        const res = await fetch( 'http://localhost:3500/directline/token', { method: 'POST' } );
        const { token: directLineToken } = await res.json();

        sessionStorage['token'] = directLineToken;
        token = directLineToken;
      }

      if (conversationId) {
        const res = await fetch(`https://directline.botframework.com/v3/directline/conversations/${ conversationId }`, {
          method: 'GET',
          headers: {
            'Authorization': `Bearer ${ token }`,
          },
        });

        let { conversationId } = await res.json();
        sessionStorage['conversationId'] = conversationId;
      } 

      const directLine = createDirectLine({
        token,
        webSockets: true,
        watermark: 10
      });

      window.WebChat.renderWebChat( {
        directLine: directLine,
      }, document.getElementById( 'webchat' ) );
      document.querySelector( '#webchat > *' ).focus();
    } )().catch( err => console.error( err ) );
    </script>
  </body>
</html>

这是生成令牌的代码。我将此附加到我的机器人中的 index.js 文件的末尾。您也可以将其作为单独的项目运行。

当我在本地运行我的机器人时,端点变得可用。如果您正在运行 C# 机器人,您应该能够执行类似的操作。此处使用的端口应与上述directline/token 调用中引用的端口相同。

directLineSecret 是通过 .env 文件存储和访问的。

/**
 * Creates token server
 */
const bodyParser = require('body-parser');
const request = require('request');
const corsMiddleware = require('restify-cors-middleware');

const cors = corsMiddleware({
  origins: ['*']
});

// Create server.
let tokenServer = restify.createServer();
tokenServer.pre(cors.preflight);
tokenServer.use(cors.actual);
tokenServer.use(bodyParser.json({
  extended: false
}));
tokenServer.dl_name = 'DirectLine';
tokenServer.listen(process.env.port || process.env.PORT || 3500, function() {
  console.log(`\n${ tokenServer.dl_name } listening to ${ tokenServer.url }.`);
});

// Listen for incoming requests.
tokenServer.post('/directline/token', (req, res) => {
  // userId must start with `dl_`
  const userId = (req.body && req.body.id) ? req.body.id : `dl_${ Date.now() + Math.random().toString(36) }`;
  const options = {
    method: 'POST',
    uri: 'https://directline.botframework.com/v3/directline/tokens/generate',
    headers: {
      'Authorization': `Bearer ${ process.env.directLineSecret }`
    },
    json: {
      User: {
        Id: userId
      }
    }
  };
  request.post(options, (error, response, body) => {
    if (!error && response.statusCode < 300) {
      res.send({
        token: body.token
      });
    } else {
      res.status(500);
      res.send('Call to retrieve token from DirectLine failed');
    }
  });
});

希望有帮助!

重新更新 - 2021 年 8 月 6 日

从技术上讲,上面 HTML 中的脚本可以正常工作,尽管它的实现不是很清楚。我提供了这个简化代码的 sn-p。

另外,请注意下面的第一行:CDN 此后略有变化。当前的稳定版本来自latest,而不是master

<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
  ( async function () {
    let { token, conversation_Id } = sessionStorage;

    if ( !token ) {
      const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
      const { token: directLineToken, conversationId } = await res.json();

      sessionStorage[ 'token' ] = directLineToken;
      sessionStorage[ 'conversation_Id' ] = conversationId
      token = directLineToken;
    }

    const directLine = createDirectLine( {
      token
    } );

    window.WebChat.renderWebChat( {
      directLine: directLine,
    }, document.getElementById( 'webchat' ) );
    document.querySelector( '#webchat > *' ).focus();
  } )().catch( err => console.error( err ) );
</script>

【讨论】:

  • 感谢您的回复!你绝对是对的,这似乎是要走的路。我刚刚开始使用它来维持对话,现在是时候进行讨厌的格式了。
  • 嗨,我知道这篇文章并不新鲜,但它对我有很大帮助。你确定上面的代码是正确的。似乎对话ID仅在存储中已知时才存储。那么它是如何第一次存储的呢?谢谢。
  • @StevenKanberg 你能解释一下为什么一开始你得到了令牌和conversationId,然后你又打电话来得到conversationId吗?我也看不出第二个电话是如何工作的。它得到https://directline.botframework.com/v3/directline/conversations/${ conversation_Id },但conversation_Id 始终为空,如if ( !conversation_Id ) { 上方的行中所检查的那样
  • 我也不明白这是怎么回事。 conversationId 仅被提取并放入 sessionStorage。你在哪里使用它?当我尝试此操作时,获取 ...directline/conversations/${ conversation_Id } 失败并显示“安全令牌对此对话无效”
  • @Martijn,感谢您指出我提供的更新中的不足之处。我不知道那是怎么弄得这么混乱的。我重新更新了原始编辑,使其更简单。我确实在代码中留下了“conversationId”以供参考,因为有些人更喜欢检查它的存在与令牌。它也用于其他一些 API 调用。
猜你喜欢
  • 1970-01-01
  • 2021-03-19
  • 1970-01-01
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多