I'm using Microsoft bot framework to create a bot and using direct channel to incorporate into web application.During the conversation,I need to bookmark or like the message or response from the bot.
Bot Framework doesn't implement this functionailty in SDKs. You can leverage middleware feature to implement it your self.
General idea is, you can save every activity message pairs with your users. And create a global message handlers for mark or like or detect every message in middleware to check wether user said mark or like. When you can marked the mard tag for the last message you saved previously.
For the sample of Middleware usage, refer to https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-Middleware for C# and https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/capability-middlewareLogging for Node.js.
Any further concern, please feel free to let me know.
Implement the IActivityLogger which is there in the Microsoft.Bot.Builder.History namespace to store/bookmark the IMessageActivity message into a DB or a cache.
IActivityLogger will intercept every message from your dialog which implements IDialog interface.
This is for intercepting every message that is sent and recieved to-fro from the user and the bot.
1) For Dialogs implementing the IDialog interface:
using Microsoft.Bot.Builder.History;
using Microsoft.Bot.Connector;
using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Threading.Tasks;
namespace DemoBot.Dialogs
{
public class Logger : IActivityLogger
{
private readonly IMongoClient client;
private readonly IMongoCollection<BsonDocument> collection;
public Logger()
{
client = new MongoClient();
collection = client.GetDatabase("test").GetCollection<BsonDocument>("botLog");
}
public Task LogAsync(IActivity activity)
{
IMessageActivity msgToBeLogged = activity.AsMessageActivity();
BsonDocument objectToBeLogged = new BsonDocument
{
{ "messageText", new BsonString(msgToBeLogged.Text) },
{ "timeStamp", new BsonDateTime(Convert.ToDateTime(msgToBeLogged.Timestamp)) },
{ "recipientId", new BsonString(msgToBeLogged.Recipient.Id) },
{ "fromId", new BsonString(msgToBeLogged.From.Id) },
{ "conversationId", new BsonString(msgToBeLogged.Conversation.Id) },
{ "fromName", new BsonString(msgToBeLogged.From.Name) },
{ "toName", new BsonString(msgToBeLogged.Recipient.Name) },
{ "channnel", new BsonString(msgToBeLogged.ChannelId) },
{ "serviceUrl",new BsonString(msgToBeLogged.ServiceUrl) },
{ "locale", new BsonString(msgToBeLogged.Locale)}
};
return Task.Run(() =>
{
LogIntoDB(objectToBeLogged);
});
}
public void LogIntoDB(BsonDocument activityDetails)
{
collection.InsertOne(activityDetails);
}
}
}
2) For Dialogs that inherit the LuisDialog class, write the logging code in the DispatchToIntentHandler method, as it will the incoming message will pass through that method to resolve into the appropriate handler:
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DemoBot.Dialogs
{
[Serializable]
public class RootDialog : LuisDialog<object>
{
public Task StartAsync(IDialogContext context)
{
return Task.Run(() => { context.Wait(MessageReceived); });
}
protected override Task DispatchToIntentHandler(IDialogContext context, IAwaitable<IMessageActivity> item, IntentRecommendation bestIntent, LuisResult result)
{
IMessageActivity msgToBeLogged = context.MakeMessage();
BsonDocument objectToBeLogged = new BsonDocument
{
{ "messageText", new BsonString(msgToBeLogged.Text) },
{ "timeStamp", new BsonDateTime(Convert.ToDateTime(msgToBeLogged.Timestamp)) },
{ "recipientId", new BsonString(msgToBeLogged.Recipient.Id) },
{ "fromId", new BsonString(msgToBeLogged.From.Id) },
{ "conversationId", new BsonString(msgToBeLogged.Conversation.Id) },
{ "fromName", new BsonString(msgToBeLogged.From.Name) },
{ "toName", new BsonString(msgToBeLogged.Recipient.Name) },
{ "channnel", new BsonString(msgToBeLogged.ChannelId) },
{ "serviceUrl",new BsonString(msgToBeLogged.ServiceUrl) },
{ "locale", new BsonString(msgToBeLogged.Locale)}
};
Task.Run(() =>
{
LogIntoDB(objectToBeLogged);
});
return base.DispatchToIntentHandler(context, item, bestIntent, result);
}
public void LogIntoDB(BsonDocument activityDetails)
{
collection.InsertOne(activityDetails);
}
public Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
{
return Task.Run(() =>
{
context.Wait(MessageReceived);
});
}
}
}
For Logging I'm using MongoDB, but you can use SQL Server also if you wish.
And lastly inject the dependencies in your global.asax.cs file using Autofac IoC.
using Autofac;
using DemoBot.Dialogs;
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Routing;
namespace DemoBot
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Conversation.UpdateContainer(builder =>
{
builder.RegisterType<Logger>().AsImplementedInterfaces().InstancePerDependency();
});
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
Related
I am developing a chat application in xamarin.forms.The viewmodel binded to my chat listview page have an API call , which will fetch the chat data and bind to the listview.The API will call only once ie; when we open the page. What I am trying to do is call the API every 10 seconds and update the listview if there are new messages.But what happening is instead of updating the list, it duplicates the entire data.I think it is normal that if the API called again, it will rebind the entire data. How can I make this update the listview if any new message available? like a chat APP works.Any help or guidance is appreciated.
The API data will be assigned to incoming and outgoing cell according to a parameter.
My viewmodel;
public class ChatPageViewModel : INotifyPropertyChanged
{
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public INavigation Navigation { get; set; }
public string APropertyToSet { get; set; }
public ObservableCollection<NCMessage> Messages { get; set; } = new ObservableCollection<NCMessage>();
public ObservableCollection<ChatData> ChatListObj { get; set; }
public ChatPageViewModel(INavigation navigation)
{
// This is how I call the timer
Device.StartTimer(TimeSpan.FromSeconds(10), () =>
{
Device.BeginInvokeOnMainThread(async () =>
{
await loadChatList();
});
return true;
});
// <--------------- Load chat List API-------------------->
async Task loadChatList()
{
await Task.Run(async () =>
{
try
{
// API call is the dedicated class for makin API call
APICall callForNotificationList = new APICall("apicallUrl/CallChatList", null, null, "GET");
try
{
ChatListObj = callForNotificationList.APICallResult<ObservableCollection<ChatData>>();
if (ChatListObj[0].results.Count != null && ChatListObj[0].results.Count != 0)
{
if (ChatListObj[0].success)
{
foreach (var item in ChatListObj[0].results)
{
if (item.type == "user")
{
if (!string.IsNullOrEmpty(item.message))
{
var message = new NCMessage
{
Text = item.message.ToString(),
IsIncoming = "True"
};
Messages.Add(message);
}
}
}
}
else
{
//error message
}
}
else
{
//error message
}
}
catch (Exception e)
{
}
}
catch (Exception ex)
{
}
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
My chat XAML
<ListView
ItemTemplate="{StaticResource MessageTemplateSelector}"
ItemsSource="{Binding Messages,Mode=OneWay}"
Margin="0"
BackgroundColor="Transparent"
SelectionMode="None"
FlowDirection="RightToLeft"
HasUnevenRows="True" x:Name="ChatList"
VerticalOptions="FillAndExpand"
SeparatorColor="Transparent"
>
</ListView>
My XAML.cs
public partial class ChatPage : ContentPage
{
ChatPageViewModel vm;
public ChatPage()
{
InitializeComponent();
this.BindingContext = vm = new ChatPageViewModel(Navigation);
}
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.Delay(2000);
await vm.loadChatList();
}
}
you are doing this for every message you retrieve from your API without checking to see if the message is already in the collection
Messages.Add(message);
you have (at least) three options
clear the entire Messages collection before calling the API
check if a message exists before adding it to the collection
modify the API call to only return new messages
The usual chat systems work using Socket connections, so that server can push just the new messages that it has received, as opposed to your current pull based system. Take a look at SignalR if you want to pursue that path, here's a good example - Real Time Chat App
As for your current code, there are a few possibilities:
As #Jason has mentioned, you could clear out the existing messages and add all of them again, you should use RangeObservableCollection so that it doesn't redraw the list every time you add a message
You could maintain a Id for all the chat messages and send the Id in your API call, ideally filtering should happen the server side, no point in extra load on the client.
new APICall("apicallUrl/CallChatList?from_id=<last_message_id>", null, null, "GET")
If there are no last_message_id, send all the data. This eliminates unnecessary data transfer, as you'll send only 1 new message than the 10k previous messages :)
I order to apply some navigationBar properties (like as the background image) for different page, I think to have a condition on my custom NavigationRenderer.
My idea is to have some condition like (in my working code)
public class CustomNavigationRenderer : NavigationRenderer
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
if (pagePushed is 1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else (ahother page){
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
}
that allows me to have at least a condition to apply a different navigation properties. Another way is to have 2 Navigationrenderer class but I think is not possible.
Any idea to how do that?
If you look at the source code for NavigationRenderer here, you will notice there are quite a few methods and callbacks you can take advantage of.
I would suggest you can do something like this:
1) Code for your custom NavigationRenderer (iOS project, you will have to do something similar on Android):
using System.Threading.Tasks;
using MyProject.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(NavigationPage), typeof(NavRenderer))]
namespace MyProject.iOS
{
public class NavRenderer : NavigationRenderer
{
protected override async Task<bool> OnPushAsync(Page page, bool animated)
{
var result = await base.OnPushAsync(page, animated);
if(result)
{
if (page is IMyPageType1)
{
NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
NavigationBar.ShadowImage = new UIImage();
}
else if(page is IMyPageType2)
{
var img = UIImage.FromBundle("MyImage");
NavigationBar.SetBackgroundImage(img, UIBarMetrics.Default);
}
}
return result;
}
}
}
2) Based on the code above, you need to add two interfaces. These should be located in the same project / dll where your Pages are located (all your Xamarin.Forms UI):
public interface IMyPageType1
{
}
public interface IMyPageType2
{
}
3) Now everything that's remaining is implement the interfaces on the pages where you need it. For example:
public partial class MyPage1 : ContentPage, IMyPageType1
{
//...
}
From here, possibilities are endless! You can add for example a method to IMyPageType1 that would return a color, and then inside your renderer, once you know the page being pushed is implementing IMyPageType1, you can call the method and get the color to use.
I need to print a picture on client side. I used this as a template. My PrintUI looks like this:
#Override
protected void init(VaadinRequest request) {
Item item = ..get item ..
StreamResource imageStream = ... build image dynamically ...
Image image = new Image(item.getName(), imageStream);
image.setWidth("100%");
setContent(image);
setWidth("100%");
// Print automatically when the window opens
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
}
This works so far in IE but in chrome it opens the printing preview showing an empty page. The problem is that the image is loaded in some way that chrome does not wait for it and starts the printing preview immideatly.
To verify this, I tried: (setting a 5sec timeout)
JavaScript.getCurrent().execute("setTimeout(function() {print(); self.close();}, 0);");
Then it works in IE and Chrome, but its of course an ugly hack, and if the connection is slower than 5sec, then again it will fail.
In pure JS it would work like this, but Im not sure how to reference the element from vaadin in cient-side js. Any ideas?
You can use AbstractJavascriptExtension.
Example extension class:
#JavaScript({ "vaadin://scripts/connector/wait_for_image_load_connector.js" })
public class WaitForImageLoadExtension extends AbstractJavaScriptExtension {
private List<ImageLoadedListener> imageLoadedListeners = new ArrayList<>();
public interface ImageLoadedListener {
void onImageLoaded();
}
public void extend(Image image) {
super.extend(image);
addFunction("onImageLoaded", new JavaScriptFunction() {
#Override
public void call(JsonArray arguments) {
for (ImageLoadedListener imageLoadedListener : imageLoadedListeners) {
if (imageLoadedListener != null) {
imageLoadedListener.onImageLoaded();
}
}
}
});
}
public void addImageLoadedListener(ImageLoadedListener listener) {
imageLoadedListeners.add(listener);
}
}
and javascript connector (placed in wait_for_image_load_connector.js) with the waiting method you have linked:
window.your_package_WaitForImageLoadExtension = function() {
var connectorId = this.getParentId();
var img = this.getElement(connectorId);
if (img.complete) {
this.onImageLoaded();
} else {
img.addEventListener('load', this.onImageLoaded)
img.addEventListener('error', function() {
alert('error');
})
}
}
Then you can do something like that:
Image image = new Image(item.getName(), imageStream);
WaitForImageLoadExtension ext = new WaitForImageLoadExtension();
ext.extend(image);
ext.addImageLoadedListener(new ImageLoadedListener() {
#Override
public void onImageLoaded() {
JavaScript.eval("print()");
}
});
In your case, when calling print() is the only thing you want to do after the image is loaded, you can also do it without server-side listener by just calling it in the connector:
if (img.complete) {
print();
} else {
img.addEventListener('load', print)
img.addEventListener('error', function() {
alert('error');
})
}
I'm trying to use a library that doesn't has a .Net SDK, but as I want to use it only to return a string, I thought I could use it's JS SDK by creating a custom WebView that returns strings (https://xamarinhelp.com/xamarin-forms-webview-executing-javascript/).
The first problem that I faced was that a CustomRenderer is not called in Xamarin.Forms until the View is added to a Page (or at least I couldn't make it be called). To fix this I added a call to Platform.CreateRenderer in each platform.
It did the trick and the CustomRenderer executed. But when I tried to call a JS function to retrieve a string, the app just hung and stayed that way.
I didn't try to insert the WebView in a Page because I want it to be independent of the page that the app is current on, and as I want a "code-only" html, I don't see the point of adding it somewhere.
My classes:
JSEvaluator
namespace MyNamespace.Views
{
public class JSEvaluator : WebView
{
public static BindableProperty EvaluateJavascriptProperty = BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(JSEvaluator), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
public JSEvaluator()
{
}
}
}
UWP Renderer
[assembly: ExportRenderer(typeof(JSEvaluator), typeof(JSEvaluatorRenderer))]
namespace MyNamespace.UWP.Renderers
{
public class JSEvaluatorRenderer : WebViewRenderer
{
public JSEvaluatorRenderer() { }
protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as JSEvaluator;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Creation and use
if (jsEvaluator == null)
{
jsEvaluator = new JSEvaluator { Source = new HtmlWebViewSource { Html = HTML.html } };
#if __ANDROID__
Xamarin.Forms.Platform.Android.Platform.CreateRenderer(jsEvaluator);
#elif __IOS__
Xamarin.Forms.Platform.iOS.Platform.CreateRenderer(jsEvaluator);
#elif WINDOWS_UWP
Xamarin.Forms.Platform.UWP.Platform.CreateRenderer(jsEvaluator);
#endif
}
Thanks for the help :)
I had to add the WebView to a page, as #SushiHangover said in the comment. With this done, it worked as expected.
The script used to be:
function OnMouseEnter()
{
renderer.material.color = Color.grey;
}
But using that is now obsolete after an update and I have no idea what the current syntax is or how one would go about finding it out. I've searched everywhere and couldn't find an answer.
Since Unity 4.6 there is a new way of handling input events. One have to use interfaces from UnityEngine.EventSystems namespace. Look at this example:
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems; // dont forget this
public class SomeController : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
private bool hovered = false;
// from IPointerEnterHandler
public void OnPointerEnter(PointerEventData eventData)
{
hovered = true;
}
// from IPointerExitHandler
public void OnPointerExit(PointerEventData eventData)
{
hovered = false;
}
// from IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)
{
// send some event
}
}
Still, you have to add collider component to your object.