azure - 页面重新加载/导航到另一个页面后,有没有办法在 MS Bot Framework 网络聊天中保留聊天历史记录?
问题描述
我试图在页面重新加载和导航到机器人链接到的网站上的其他页面期间保持用户与机器人的对话。
目前,这些操作会关闭机器人窗口并完全重新启动对话,直至再次触发机器人的欢迎消息。
有问题的机器人按照文档中的说明嵌入网页中:https ://docs.microsoft.com/bs-latn-ba/azure/bot-service/bot-service-channel-connect-webchat?view=天蓝色机器人服务 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提供的数量。
如果您将考虑使用基于 v4 react 的网络聊天产品(在上面的链接中引用),那么以下示例将为您提供您正在寻找的功能。
请注意,为简单起见,我将会话 ID 保存在会话存储中。
此外,我通过对本地运行的直线端点进行 API 调用来生成令牌。我在最后包含了代码来做同样的事情。您可以在 html 文件中针对端点传递您的直接线路密码directline/tokens/generate
,但是出于安全原因,强烈建议不要这样做。
最后,watermark
createDirectLine() 方法中使用的属性指定要显示的过去活动的数量(消息、卡片等)。
<!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 中的脚本可以工作,尽管它的实现不是很清楚。我提供了这个简化代码的片段。
另外,请注意下面的第一行: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>
推荐阅读
- arrays - 使用 moment.js 库按日期对数组中的对象进行分组
- docker - 尝试更改从主机挂载的配置文件时,在 docker 容器启动期间 chown 失败
- excel - 使用带有 Ranges 数组的 Consolidate 函数
- julia - 尝试添加不同类型的数量时出现 MethodError
- android - 访问 android 模拟文件系统调试颤振
- java - 在将 ArrayList 设置和重置为 HashMap 中的值时找不到方向
- elixir - elixir phoenix - put_flash/3 在插件中
- ruby-on-rails - Date.to_formatted_s() 似乎不像宣传的那样工作
- r - 将组的列平均值乘以整个数据的列平均值
- python - 将复杂的查询集从 django 转换为烧瓶