This commit is contained in:
João Luís Lopes Quitério 2018-09-18 19:54:04 +01:00
Родитель 9abbd0e3a5
Коммит ad5b853248
125 изменённых файлов: 6936 добавлений и 10 удалений

63
.gitattributes поставляемый Normal file
Просмотреть файл

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

408
README.md
Просмотреть файл

@ -1,14 +1,402 @@
# END-USER/EXPERT BOT SOLUTION
The end user/expert bot solution is a 2 bot product that allows the end users to query a knowledge base using natural language.
If no answer is found on the Knowledge base or if the user thinks that the answer is wrong, a team of experts is contacted using the second bot of the solution.
# Contributing
The expert bot allows the team of experts to answer to the end users' questions and to update and improve the knowledge base. All this interaction is also done through the usage of natural language.
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
This soultion allows for several end user and expert channels, however currently only has been tested for the following:
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
* End User:
* Skype
* Web Chat
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
* Expert:
* Microsoft Teams
# ARCHITECTURE AND DATA MODEL
The full solution comprises of the following items:
* End User Bot
* Expert Bot
* Knowledge Base
* Table storage
* Natural Language Understanding service
* Queue
* Event Hub
* Stream Analytics
## Tables
There are several No-SQL tables supporting the solution. Their main purpose is:
* Storing user data;
* Store the mappings between users and the questions they made;
* Store the questions that need be answered by the experts;
* Store the End user feedback regarding the response from the Knowledge base;
* Store Auditing logs for further processing by BI tools
* Store the bot context and state.
### Users
The users table stores information about the End Users of the system, namely the user id, the channel, the last time the user was active and if the user has the notifications turned on.
The Users table is partitioned by ChannelId and each row is identified by the UserId. (The pair ChannelId/UserId is always unique)
When the End User bot receives a new message, he either creates or updates a record for the user, setting the last active time to the time the message was received. When creating a user, the notifications are always enabled.
### Still Interested Table
The still interested table is used to store the mapping between the users and the questions they make.
This table contains the questions, the user, the channel and whether the answer was already given or not. (This flag is used to decide if the message coming from the experts is an update to the answer or the first time the answer is given).
The table is partitioned by the Question scentence and each row is identified by \{channelId:userId\}.
Whenever a new message arrives from the "Experts to User" queue, the bot retrieves all the users that are interested on the question (retrieves that question's partition).
### Unanswered Questions Table
The unanswered questions table is used to store which questions are pending an answer from the Experts. The table contains the question, the answer that was given from the Knowledge Base (if it exists), the question that matched on the Knowledge base (if it exists) and the date the question was posted to the experts.
There is no need to store the user information since it will be stored on the still interested table.
The table is partitioned using the hash of the question and each row identifier is calculated as the hash of the question that matched on the knowledge base. This ensures that there are no duplicates of the same question.
Whenever a new message arrives to the Expert Bot, a new record for that question is inserted on the table.
### Expert Bot Channels
The expert bot channels table stores a list of all the channels that are available for the Expert bot. This allows the bot to broadcast a question from a user to a multitude of Expert teams and individual contributers (through inividual channels such as skype).
This table is partitioned by the bot framework channelId and contains for each channel a unique identifier (guid) and the JSON serialization of a bot framework's ConversationReference object.
Everytime a message is sent to the Expert Bot, the bot checks if the channel is already registered. If it's a new channel, a new record is created
### Feedback table
The feedback table holds the information about the user feedback for a given pair question/answer. This feedback can be either negative (no answer was found or the answer was deemed wrong) or positive (the answer given by the Knowledge base was correct).
The table is partition by Positive and Negative feedbacks, and the key inside each partition is given by a unique id (GUID) that is generated for every question. The table also stores the answer given by the knowledge base, if it existed.
### Auditing
The auditing tables are used to store data that then will be used to produce reports of the usage of the solution.
There are 2 auditing tables:
* ExpertAuditing - stores all the auditing information for the expert bot;
* EndUserAuditing - stores all the auditing information for the end user bot;
### Bot Data
The bot data tables are the tables that are used by the bot framework to mainitain the Bot context and state.
There are bot data tables on this solution, one for each bot. Each one of them is a no-SQL table on Azure Table Storage. For more details on these tables check [this link](https://docs.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-state-azure-table-storage).
The tables are:
* **enduserbotdata** - stores the bot data for the End User bot;
* **expertbotdata** - stores the bot data for the Expert bot;
## Queues
This solution uses Azure Storage queues to ensure both bots can exchange messages without being tightly coupled. Since the messages are small in size and there are no needs of storing them for a long time the Service Bus Queue was not taken into account.
There are 3 queues in total in this solution:
* User to Expert queue
* Expert to User queue
* Notifications queue
### User to Expert Queue
The user to expert queue is implemented as an Azure Storage Queue.
The object that is sent in the queue has the following fields:
* **Question** - the question of the user;
* **OriginalAnswer** - the answer that was given by the Knowledge Base (if exists);
* **OriginalQuestion** - the question from the Knowledge Base that matched the user's question (if exists).
* **ExpertAnswer** - the answer of the expert (it will be blank);
* **Timestamp** - when the question was posted;
* **MessageType** - If the message is about a Wrong Answer from the Knowledge Base or if is about a question that has no answer.
### Expert to User Queue
The expert to user queue is also implemented as an Azure Storage Queue.
The object that is sent in the queue has the same structure as the object sent on the User to Expert Queue. The ExpertAnswer field will be filled this time.
### Notifications queue
The notifications queue is also implemented as an Azure Storage Queue.
The object that is sent in the queue only has one field:
* TextToSend
## Blobs
There are 2 blob containers on the solution:
* Configurations
* Files
These containers were implmented on Azure Blob Storage
### Configurations
The configurations container contains the configuration files for both bots.
As stated before, for each bot, there are 2 files - the configuration file and the message file. Hence the configurations container has the following contents:
* End User Bot Configuration;
* End User Bot Messages;
* Expert Bot Configuration;
* Expert Bot Messages;
### Files
The files container is where the files that can be downloaded by the users are stored.
Currently there will only be 2 files on this container:
* Private Knowledge Base file
* Public Knowledge Base file
## KNOWLEDGE BASE
The Knowledge Base is the core element of the solution. The knowledge base is a set of question/answer pairs with what both bots interact.
Currently the knowledge base is implemented using the Microsoft QnA Maker. This product has a Question/Answer list and has a small component of NLU, that ensures that an exact match with the question on the knowledge base is needed.
## EVENT HUBS
The event hubs are a tool that allows the processment of several events at the same time. On this solution, the Event Hub is used to send auditing events from both bots.
To ensure that the data is logically separated, there are 2 event hubs, one for the EndUser events and other for the Expert events.
## STREAM ANALYTICS
The Stream Analytics component is responsible for reading the events from the event hub and place them in the respective table (ExpertAuditing or EndUserAuditing).
To do so, the stream analytics job gets the data from each one of the event hubs and place it on the respective Azure table.
# END USER BOT
The end user bot is a bot designed to work on several channels, namely Skype and Web Chat, that allows the end user to query a knowledge base for information. If the user doesn't find the information that he uas looking for, or if the information is not correct, he has the ability to notify a team of experts to insert the correct answer on the knowledge base.
The bot uses an NLU engine to determine what are the intents of the end user. Currently, the bot has available the following intents:
* AskQuestion -- The user wants to query the knowledge base and notify the experts team if the answer is missing or is wrongly given;
* DownloadKnowledgeBase -- The user wants a file with all the knowledge base. In the end user bot the file only contains the list of questions stored on the knowledge base. The file is in a tsv (tab separated values) format;
* TurnOnNotifications -- The user wants to receive notifications about updates on the answer of his questions.
* TurnOffNotifications -- The user wants to stop receiving notifications about updates on the answer of his questions.
## EVENT FLOWS
### AskQuestion flow of events
The AskQuestion flow of events is the following:
1. User writes on the Skype window (eg. What is Azure Stack?);
2. The bot understands that the intent is AskQuestion and fowards the query to the Knowledge Base;
3. If the knowledge base didn't find the answer, a message is automatically posted to the Experts team and a record for this question/user pair is stored in table storage.
1. If otherwise, the answer is shown to the user.
1. The user is enquired if the answer given by the knowledge base is correct
2. If the answer is correct, that feedback is stored and nothing more is done.
3. If the answer is incorrect, the query is posted to the Experts teams and a record for this question/user pair is stored in table storage.
4. ...
5. When an answer to that question arrives, the bot gets all the users that are pending an answer to that question and notifies them of the answer.
6. If further updates happen to the answer, only the users that have the notifications turned on will receive them.
Note: If a question has more that a given number of "Correct" feedback than the answer is always considered correct.
### Download Knowledge Base flow of events
The Download Knowledge Base flow of events is the following:
1. User writes on the Skype window (eg. I want to download the knowledge base);
2. A link to the blob containing the file is posted by the bot. This link has an expiration time, after which it stops to work.
The file only contains a __list of the questions__ that are present on the knowledge base.
### Notification flow of events
The Notification flow of events is the following:
1. User writes on the Skype window (eg. I want to turn off/on my notifications);
2. The notifications are set to on or off depending on the choice of the user.
## AUDITING
Currently the following events are being logged on Event Hub for auditing:
* New question asked;
* Download of Knowledge Base;
* Technical Errors.
## JOBS
There are several jobs that are ran periodically on the end user side of the solution:
* the Expert to User Queue listener job;
* the Notifications Queue listener job;
* the Still Interested job;
### Expert to User Queue Listener
The Expert to User queue listner is job that listens to the Expert to User Queue. The job periodically polls the queue for new answers given by the experts. The poll interval is set to 10 seconds and it's currently not configurable.
Whenever a new message arrives, the job check which users are expecting the answer that came in the message and posts the answer to each one of these users. Users that have the notifications turned on, won't get any updates on the answers.
### Notifications Queue Listener
The notifications queue listner is job that listens to the Notification Queue. The job periodically polls the queue for new notifications. The poll interval can be configured using the _NotificationPollInterval_ key. The value is set in **minutes**.
Whenever a new message arrives, it is sent to all users that have interacted with bot.
### Still Interested Job
This job monitors the table of questions that were made by the users. It is used to ask the End User if he/she is still interested on a question that they made in the past and has yet to get an answer.
There are 2 parameters on this job:
* How frequently the job checks which questions are pending an answer; This is configurable using the key _stillInterestedPollIntervalHours_. The value is set in **hours**
* How many days without an answer before the user is questioned if he/she is still interested. This is configurable using the setting _stillInterestedTimeoutDays_. The value is set in **days**.
If the users says he/she is still interested on the question, the question is resubmited to the expert team and the number of days without an answer is reset to 0. If the user is not interested on the question, he/she won't get notified if an answer arrives.
# EXPERT BOT
## EVENT FLOWS
### New Question event flow
Whenever a new message arrives the bot will inform the experts of that event.
When the question doen't have any answer from the knowledge base, the bot posts an informational notification on each available channel stating which question has been asked.
However, if the end user thought the answer the he received from the knowledge base was wrong, then the bot will go through the following steps:
1. Checks if the question that matched on the knowledge base is the same as the one the user made.
2. If the questions match, then the user is prompt to select wheater the original answer is correct or if he wants to replace it with a new one; If they don't match, then the expert can also create a specific Q/A pair with a new answer.
3. If the user selects to replace the existing answer or to create a specific pair, he will be prompted for the answer to be stored;
4. The answer is stored (or maintained if the user opted to keep the original answer), the question is removed from the UnansweredQuestions table and the End Users are notified of the answer.
### Answer question event flow
The answer question event flow enables the expert to answer to any question he wants regardless of having being previously asked by an end user.
This flow allows the expert to add new entires to the knowledge base, to update answers that are already on the knowledge base or to answer questions from end users that are pending an answer.
The answer question event flow is the following:
1. The expert write the intention on Teams (eg. I want to answer the question What is Azure Stack?)
2. The bot will try to find a question on the utterance. If no question is found than it shows the list of pending questions and follows the List Unanswered questions flow detailed later
3. The bot will ask for the answer for that question;
4. After the bot receives the answer it will check if there is already a match for that question on the knowledge base and will do one of the following things:
1. If no match is found, then a new record is created on the knowledge base;
2. If a match is found for that same question the user is prompt to chose if he wants to keep the original answer or overwrite that record with the new answer;
3. If a match is found but the question is not the same, the user is prompt to chose if he keep the original answer, overwrite the answer with the new answer or create a specific record for the new Q/A pair.
5. When there is a change on the knowledge base, the bot is responsible to train the KB with the new data.
6. The changes on the knowledge base are sent to the end user bot through the Expert to User Queue.
### Donwload the knowledge base
The Download Knowledge Base flow of events is the following:
1. User writes his intention on the Teams window (eg. I want to download the knowledge base);
2. If no knowledge base file was generated, then it is created with a snapshot of the knowledge base;
2. A link to the blob containing the file is posted by the bot. This link has an expiration time, after which it stops to work.
The file will contain the full knowledge base, i.e. __both the questions and the answers__.
### Refresh knowledge base files
The refresh knowledge base files flow allows the expert team to update the files that are available for download containing the Knowledge base information.
The Refresh Knowledge Base flow of events is the following:
1. User writes his intention on the Teams window (eg. I want to refresh the knowledge base);
2. Both the public and private knowledge base files are recreated with a snapshot of the knowledge base at that moment.
### List unanswered questions
The list unanswered questions flow allows the expert team to get a list of all the questions that are pending an answer.
This flow has the following steps:
1. The user writes his intention on the teams channel (eg. I want to know which questions are unanswered)
2. The bot displays a list of all the questions that are pending an answer and that were asked in the last 30 days. This value can be configured on the configuration file for the Expert Bot by setting the value for the **MessageRetention** key
3. To answer a question the user can click on it.
## AUDITING
Currently the following events are being logged on Event Hub for auditing:
* New question arrived;
* Question Answered;
* Global Notification Sent;
* Download of Knowledge Base;
* Refresh of Knowledge Base File;
## JOBS
There is just one job running on the expert side of the solution, the user to expert queue listener job.
### User to Expert Queue Listener
The user to expert queue listener job monitors if new questions arrive from the end users.
Whenever a new question arrives, the job triggers the New Question event flow. It's the job's responsability to trigger the flow on all the available expert channels.
This job also populates the UnansweredQuestions table whenever there is a new question.
# INTER-BOT COMMUNICATION
The two bots communicate between them using 3 separate queues:
1. The User to Expert Queue
2. The Expert to User Queue
3. The Notifications queue
## User to expert queue
The user to expert queue is used whenever there is a new question to be posted to the Experts team.
This queue is currenlty implemented on Azure Storage Queue. The End User bot enqueues a message containing the question, the MessageType (WrongAnswer or NoAnswer) and, if applicable, the answer from the Knowledge Base.
The Expert bot has a job that polls this queue and whenever a new message is available, it is dequeued. The message is then sent to all the available channels on the Expert bot.
Note: If the Expert bot fails to deliver the message to any or all of the channels, the message doesn't return to the queue
## Expert to user queue
The expert to user queue is used to send answers that were given on the Expert bot. It's also currently implemented as an Azure Storage Queue.
Whenever there is an answer to a question that was posted by a user or when an expert proactively answers a question a new message is sent. The message contains both the question and the answer given.
The End User bot has a job that polls this queue and whenever a new message is available checks which users are waiting for an answer or have previously asked the same question and notifies them.
Note: If the End User bot fails to deliver the message, the message doesn't return to the queue.
Note2: Only users that have the notifications turned on will receive the updates on the answer. If they have them turned off, their record for that specific question is deleted when the new answer arrives. This means that is the user turns the notifications on again, he will only receive notifications for questions that he makes afterwards.
## Notifications queue
The notifications queue is used for global notification sent by the Expert team. These notifications will be sent to all End Users even if they have turned off their notifications.
The End user bot has a job that polls this queue and whenever a message is available it dequeus it and broadcasts it to all users.
Note: If the End User bot fails to deliver the message, the message doesn't return to the queue.
#CONFIGURATIONS AND MESSAGES
## CONFIGURATIONS
The configurations for the bot are stored in a .JSON file in Blob storage.
After changing the file either force a web application restart or wait a maximum of one hour for the changes to be effective.
## MESSAGES
The messages that are posted by the bot are stored in a .JSON file in Blob storage.
After changing the file force a web application restart for the changes to be effective.
## NATURAL LANGUAGE TRAINING
To Configure the bot's NLU model go through the following steps:
1. Go to [www.luis.ai](http://www.luis.ai)
2. Sign in with the bot's credentials - eaibot@outlook.com
3. Select the resepetive NLU model
![Select Model](/Assets/LuisSelectModel.png)
4. Select the Review Endpoint utterances option
![Review Endpoint Utterances](/Assets/ReviewEndpointUtterances.png)
1. Select the aligned intent that best suits each utterance
![AlignedIntent](/Assets/AlignedIntent.png)
2. Identify the entities present in the utterance (if any).
1. To do so, left click on the first word and then on the last word of the entity and select the entity type
![SelectEntities](/Assets/SelectEntities.png)
3. Click on the check button (Add to aligned intent)
![AddToIntent](/Assets/AddToIntent.png)
5. After setting the intents and/or the entities train the model (the training will ensure that the new utterances are taken into account);
![Train](/Assets/Train.png)
6. Publish the newly trained model (the changes made to the model are only effective once the model is publish)
![Publish stp 1](/Assets/Publish1.png)
![Publish step 2](/Assets/Publish2.png)
## KNOWLEDGE BASE TRAINING
To edit the knowledge base go through the following steps:
1. Go to [www.qnamaker.ai](http://www.qnamaker.ai)
2. Sign in with the bot's credentials - eaibot@outlook.com
3. Select the service that holds the Knowledge Base
![Select QnA Service](/Assets/qnaService.png)
4. On this screen you can edit an answer, edit a question, add alternative phrasings, delete a QnA Pair or add a new pair (user the + Add new QnA Pair)
![Add qna pair](/Assets/addQnaPair.png)
5. After editing the Knowledge Base click on Save and Retrain.
![Save and retrain](/Assets/SaveRetrain.png)
6. After the training finishes, publish the updated Knowledge Base
![Publish step 1](/Assets/qnaPublish1.png)
![Publish step 2](/Assets/qnaPublish2.png)
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

Просмотреть файл

@ -0,0 +1,26 @@
{
"ReceivedNewMessage": "I'm going to check that question on my knowledge base",
"BotWelcome": "Hello, I'm your Azure Stack community bot. Ask me any question you have about this topic",
"WhatMoreCanIDo": "What more do you want to ask me?",
"FoundAnswer": "I found the answer to your question in my knowledge base.<br/>The answer is: **{0}**",
"IsMessageCorrect": "Does this answer your question?",
"GladToHelp": "I'm glad I was able to help you",
"SorryToNotHelpYou": "I'm sorry this was not the answer you were looking for.<br/>I'm going to submit your question to the experts to see if I can get more informtaion for you.<br/>I'll come back to you as soon as I have more information",
"NoAnswerOnQna": "I'm sorry but I don't have that information on my knowledge base yet. I'm going to submit your question to the experts on this matter and I'll come back to you as soon as I have an answer.",
"ReceivedAnswer": "I've got the answer for your question **\"{0}\"**.<br/>The answer is **\"{1}\"**",
"ReceivedOriginalAnswer": "I've got feedback about your question **\"{0}\"**.<br/>The expert community thinks that the answer that I gave to you is indeed the correct one: **\"{1}\"**",
"ExceptionMessage": "I'm experiencing some technical problems right now. Try again later on the day. If the problem presists please contact the community adminstrator.",
"StillInterestedPrompt": "Are you still interested in knowing the answer to the question **{0}**?",
"ResendingQuestion": "I'm going to resend your question",
"ForgettingQuestion": "Sure, I'm not going to bother you if the answer for this question comes.",
"Confidential": "I'm sorry but the answer for your question has been marked confidential and can't be shared on a public channel. Please re-ask the question in a private channel.",
"DidntUnderstand": "I'm sorry, i didn't understand your question",
"KBNotAvailable": "I'm sorry but the knowledge base file is not available at the moment",
"KbLink": "Please click on this [link]({0}) to download the knowledge base. This link is valid for the next {1} minutes.",
"NewNotification": "I have a new information for you: **{0}**",
"ReceivedAnswerUpdate": "I have received an update for a question you posted on the past.<br/>**Question**: {0}<br/>**Answer:** {1}",
"NotificationsTurnedOn": "The answer update notifications are now turned on",
"NotificationsTurnedOff": "The answer update notifications are now turned off",
"issueNotUnderstood": "Sorry, I didn't understand what you want to send. Please use the following format: I want to report an issue: issue to send",
"goingToSendIssue": "I'm going to send the following issue to the experts: {0}"
}

Просмотреть файл

@ -0,0 +1,36 @@
{
"PostMessageToExpertsNoAnswer": "There is a new question that will need an answer.<br/>**{0}**<br/>",
"PostMessageToExpertsWrongAnswer": "There is an answer that the user thinks it's wrong.<br/>Question: **\"{0}\"**<br/>Given Answer: **\"{1}\"**<br/>Matched question from KB: **\"{2}\"**",
"PostMessageToExpertsWrongAnswerCard": "There is an answer that the user thinks it's wrong.<br/>Question: <b>\"{0}\"</b><br/>Given Answer: <b>\"{1}\"</b><br/>Matched question from KB: <b>\"{2}\"</b>",
"BotWelcome": "Hello team, I'm the bot responsible to manage this community knowledge base.<br/>Let me know how can I be useful for the team",
"QuestionToAnswer": "What is the question you want to answer?",
"Answer": "What is the answer for that question?",
"GoingToStoreAnswer": "I'm going to store the answer on the knowledge base",
"ThankYou": "I'm happy because now I know more stuff :D",
"NoneIntent": "I'm sorry, i didn't get what you want to do",
"UnansweredQuestionsList": "Here is the list of questions that are without an answer in the last {0} days.<br/>If you know the answer to any of them, pelese click on the question.",
"UnanweredQuestionTempalate": "I want to answer question {0}",
"NoQuestionOrMatch": "I'm sorry, I haven't found the question on your sentence",
"ChooseQuestion": "Which one of these unanswered questions you want to answer?",
"askForAnswer": "What is the answer for **\"{0}\"**?",
"KeepingOriginal": "Roger that!!! I'm keeping the original answer.",
"KeepingBoth": "Roger that!!! I'm going to keep both answers on my knowledge base.",
"KeepingNew": "Roger that!!! I'm going to store the new answer and delete the old one.",
"KeepNew": "Replace the old answer with the new one",
"KeepBoth": "Create a new record for the new answer.",
"KeepOriginal": "Keep the original answer.",
"KeepNewWrong": "Replace the answer with a new one.",
"KeepBothWrong": "Create a new record for this question.",
"KeepOriginalWrong": "Keep the original answer.",
"ConfirmNewAnswer": "There is already a QnA pair on my knowledge base for that question.<br/>Current question: <b>\"{0}\"</b><br/>KB Match: <b>\"{1}\"</b><br/>Answer: <b>\"{2}\"</b><br/>What do you want to do?",
"ExceptionMessage": "I'm experiencing some technical problems right now. Try again later on the day. If the problem presists please contact the community adminstrator.",
"WhatMoreCanIDo": "What more do you want to ask me?",
"QuestionList": "Here is the list of questions that I know how to answer<br/>{0}",
"QuestionListItem": " * {0}",
"KnowledgeBaseRefreshed": "The knowledge base files have been refreshed with the newest KB entries",
"knowledgeBaseDownload": "Please click on this [link]({0}) to download the knowledge base. This link is valid for the next {1} minutes.",
"NoUnansweredQuestions": "There are no questions from users that are pending an answer.",
"NotificationNotFound": "I'm sorry but I didn't understand the notification you want to send. Please use the following format \"Send the following notification: This is a notification\"",
"NotificationSent": "The notification \"{0}\" was sent to all the users",
"NewIssue" : "There is a new issue raised by a user: **{0}**"
}

Двоичные данные
src/Assets/AddToIntent.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 24 KiB

Двоичные данные
src/Assets/AlignedIntent.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 38 KiB

Двоичные данные
src/Assets/LuisSelectModel.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 25 KiB

Двоичные данные
src/Assets/Publish1.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
src/Assets/Publish2.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 26 KiB

Двоичные данные
src/Assets/ReviewEndpointUtterances.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 26 KiB

Двоичные данные
src/Assets/SaveRetrain.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 48 KiB

Двоичные данные
src/Assets/SelectEntities.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 47 KiB

Двоичные данные
src/Assets/Train.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Двоичные данные
src/Assets/addQnaPair.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 48 KiB

Двоичные данные
src/Assets/qnaPublish1.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 49 KiB

Двоичные данные
src/Assets/qnaPublish2.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 21 KiB

Двоичные данные
src/Assets/qnaService.PNG Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 21 KiB

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.BotCommon
{
public class AddressKey : IAddress
{
public string BotId { get; set; }
public string ChannelId { get; set; }
public string ConversationId { get; set; }
public string ServiceUrl { get; set; }
public string UserId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Http;
namespace ModernApps.CommunityBot.BotCommon
{
[Serializable]
public abstract class DialogBase : LuisDialog<IDialogResult>, IDialog<IDialogResult>
{
protected const string GREETINGTAG = @"\[greeting\]";
protected IConfigurationProvider configuration =>(IConfigurationProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IConfigurationProvider));
protected IMessageProvider messageProvider => (IMessageProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IMessageProvider));
protected IQnAMakerProvider qnaMakerProvider => (IQnAMakerProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IQnAMakerProvider));
protected ITableStorageProvider tableStorageProvider => (ITableStorageProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(ITableStorageProvider));
protected IQueueProvider queueProvider => (IQueueProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IQueueProvider));
protected EventProviderHub eventProvider => (EventProviderHub)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(EventProviderHub));
public DialogBase()
{
}
public DialogBase(ILuisService[] services) : base(services)
{
}
public async sealed override Task StartAsync(IDialogContext context)
{
await OnStart(context);
if (services.Any())
{
await base.StartAsync(context);
}
}
public virtual async Task OnStart(IDialogContext context)
{
//EMPTY
}
protected override LuisRequest ModifyLuisRequest(LuisRequest request)
{
request.Query = Regex.Replace(request.Query, @"[""']", "");
return base.ModifyLuisRequest(request);
}
protected string GetLuisEntity(LuisResult result, EntityRecommendation question)
{
return result.Query.Substring(question.StartIndex.Value, question.EndIndex.Value - question.StartIndex.Value +1);
}
protected void ClearContextData(IDialogContext context)
{
context.ConversationData.Clear();
context.UserData.Clear();
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.BotCommon
{
public class DialogResult : IDialogResult
{
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.BotCommon.Events
{
public abstract class DialogEventBase : EventBase
{
public string ChannelId { get; set; }
public string UserId { get; set; }
public string ConversationId { get; set; }
protected DialogEventBase(IDialogContext context)
{
ChannelId = context.Activity.ChannelId;
UserId = context.Activity.From.Id;
ConversationId = context.Activity.Conversation.Id;
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
namespace ModernApps.CommunityBot.BotCommon
{
public interface IDialogResult
{
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using ModernApps.CommunitiyBot.Common.Resources;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
namespace ModernApps.CommunityBot.BotCommon
{
[BotAuthentication]
public abstract class MessagesControllerBase : ApiController
{
protected IMessageProvider messageProvider;
public MessagesControllerBase(IMessageProvider messageProvider)
{
this.messageProvider = messageProvider;
}
protected void RemoveBotNameFromMessage(Activity message)
{
if (message.Type == ActivityTypes.Message)
{
var mentions = message.GetMentions();
foreach (var mention in mentions)
{
message.Text = message.Text.Replace(mention.Text, "");
}
}
}
protected Activity HandleSystemMessage(Activity message, string welcomeMesseage)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
if (message.MembersAdded.Any(x => x.Id == message.Recipient.Id))
{
var connector = new ConnectorClient(new Uri(message.ServiceUrl));
var reply = message.CreateReply(welcomeMesseage);
connector.Conversations.ReplyToActivity(reply);
}
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}

Просмотреть файл

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{13DFF99A-6DE1-4826-A7AD-D078DD08E2D3}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernApps.CommunityBot.BotCommon</RootNamespace>
<AssemblyName>ModernApps.CommunityBot.BotCommon</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=4.6.2.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.4.6.2\lib\net45\Autofac.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi, Version=4.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.WebApi2.4.1.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
</Reference>
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
<HintPath>..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
</Reference>
<Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\packages\EntityFramework.6.2.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.Documents.Client, Version=1.19.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.DocumentDB.1.19.1\lib\net45\Microsoft.Azure.Documents.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Azure.KeyVault.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.KeyVault.Core.2.0.4\lib\net45\Microsoft.Azure.KeyVault.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.3.13.0.3\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.3.13.0.3\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder.Azure, Version=3.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.Azure.3.2.5\lib\net46\Microsoft.Bot.Builder.Azure.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder.Extensions, Version=3.9.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Bot.Builder.Extensions.3.9.1\lib\net46\Microsoft.Bot.Builder.Extensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder.History, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.History.3.13.0.3\lib\net46\Microsoft.Bot.Builder.History.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Connector, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Connector.3.13.0.3\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Connector.Teams, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Connector.Teams.0.8.0\lib\net46\Microsoft.Bot.Connector.Teams.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Edm, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Edm.5.8.3\lib\net40\Microsoft.Data.Edm.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.OData.5.8.3\lib\net40\Microsoft.Data.OData.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Services.Client, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Services.Client.5.8.3\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Logging, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Logging.5.2.0\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.40306.1554, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocols, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocols.5.2.0\lib\net451\Microsoft.IdentityModel.Protocols.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.5.2.0\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Tokens.5.2.0\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Rest.ClientRuntime.2.3.10\lib\net452\Microsoft.Rest.ClientRuntime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Storage, Version=8.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAzure.Storage.8.7.0\lib\net45\Microsoft.WindowsAzure.Storage.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Polly, Version=5.8.0.0, Culture=neutral, PublicKeyToken=c8a3ffc3f8f825cc, processorArchitecture=MSIL">
<HintPath>..\packages\Polly-Signed.5.8.0\lib\net45\Polly.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.5.2.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Net" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Numerics" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Spatial, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Spatial.5.8.3\lib\net40\System.Spatial.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="AddressKey.cs" />
<Compile Include="DialogBase.cs" />
<Compile Include="DialogResult.cs" />
<Compile Include="Events\DialogEventBase.cs" />
<Compile Include="IDialogResult.cs" />
<Compile Include="MessageControllerBase.cs" />
<Compile Include="PipelineOverrides\DefaultExceptionMessageOverrideModule.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UserToBotFilter.cs" />
<Compile Include="WebApiConfigBase.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModernApps.CommunityBot.Common\ModernApps.CommunityBot.Common.csproj">
<Project>{61D923CB-71FC-4BED-A485-78CC2D381A55}</Project>
<Name>ModernApps.CommunityBot.Common</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Azure.DocumentDB.1.19.1\build\Microsoft.Azure.DocumentDB.targets" Condition="Exists('..\packages\Microsoft.Azure.DocumentDB.1.19.1\build\Microsoft.Azure.DocumentDB.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Azure.DocumentDB.1.19.1\build\Microsoft.Azure.DocumentDB.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Azure.DocumentDB.1.19.1\build\Microsoft.Azure.DocumentDB.targets'))" />
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Autofac;
using Microsoft.Bot.Builder.Autofac.Base;
using Microsoft.Bot.Builder.Dialogs.Internals;
namespace ModernApps.CommunityBot.BotCommon.PipelineOverrides
{
public class DefaultExceptionMessageOverrideModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterKeyedType<UserToBotFilter, IPostToBot>().InstancePerLifetimeScope();
builder.RegisterAdapterChain<IPostToBot>(
typeof(EventLoopDialogTask),
typeof(SetAmbientThreadCulture),
typeof(QueueDrainingDialogTask),
typeof(PersistentDialogTask),
typeof(ExceptionTranslationDialogTask),
typeof(SerializeByConversation),
typeof(UserToBotFilter),
typeof(LogPostToBot)
).InstancePerLifetimeScope();
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModernApps.CommunityBot.BotCommon")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModernApps.CommunityBot.BotCommon")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("13dff99a-6de1-4826-a7ad-d078dd08e2d3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs.Internals;
using System;
using Microsoft.Bot.Connector;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Resources;
using Microsoft.Bot.Builder.Internals.Fibers;
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using System.Web.Http;
using Newtonsoft.Json;
using ModernApps.CommunitiyBot.Common.Resources;
namespace ModernApps.CommunityBot.BotCommon
{
public class UserToBotFilter : IPostToBot
{
private readonly IPostToBot inner;
private readonly IBotToUser botToUser;
private readonly ResourceManager resources;
private readonly TraceListener trace;
private IMessageProvider messageProvider;
public UserToBotFilter(IPostToBot inner, IBotToUser botToUser, ResourceManager resources, TraceListener trace)
{
this.messageProvider = (IMessageProvider)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IMessageProvider));
SetField.NotNull(out this.inner, nameof(inner), inner);
SetField.NotNull(out this.botToUser, nameof(botToUser), botToUser);
SetField.NotNull(out this.resources, nameof(resources), resources);
SetField.NotNull(out this.trace, nameof(trace), trace);
}
async Task IPostToBot.PostAsync(IActivity activity, CancellationToken token)
{
try
{
await inner.PostAsync(activity, token);
}
catch (Exception ex)
{
trace.WriteLine(ex.Message);
var messageActivity = activity.AsMessageActivity();
if (messageActivity != null)
{
await botToUser.PostAsync(messageProvider.GetMessage("ExceptionMessage"), cancellationToken: token);
await botToUser.PostAsync(messageProvider.GetMessage("WhatMoreCanIDo"), cancellationToken: token);
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, messageActivity))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(default(CancellationToken));
var stack = scope.Resolve<IDialogStack>();
stack.Reset();
botData.ConversationData.Clear();
await botData.FlushAsync(default(CancellationToken));
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Web.Http;
using System;
using Autofac.Integration.WebApi;
using System.Web.Http.Dependencies;
using Autofac;
using ModernApps.CommunitiyBot.Common.Providers;
using Microsoft.IdentityModel.Protocols;
using System.Configuration;
using System.Reflection;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Builder.Azure;
using ModernApps.CommunityBot.BotCommon.PipelineOverrides;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunityBot.Common.Events;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunitiyBot.Common.Resources;
namespace ModernApps.CommunityBot.BotCommon
{
public abstract class WebApiConfigBase
{
private Assembly entryAssembly;
private string botDataTableName;
private string configFileName;
private string messageFileName;
public WebApiConfigBase(Assembly entryAssembly, string botDataTableName, string configFileName,string messageFileName)
{
this.entryAssembly = entryAssembly;
this.botDataTableName = botDataTableName;
this.configFileName = configFileName;
this.messageFileName = messageFileName;
}
public void Register(HttpConfiguration config)
{
var store = new TableBotDataStore(ConfigurationManager.AppSettings["StorageConnectionString"], botDataTableName);
Conversation.UpdateContainer(builder =>
{
builder.RegisterModule(new DefaultExceptionMessageOverrideModule());
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
builder.Register(c => new CachingBotDataStore(store,
CachingBotDataStoreConsistencyPolicy.LastWriteWins))
.As<IBotDataStore<BotData>>()
.AsSelf()
.InstancePerLifetimeScope();
});
// Json settings
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Newtonsoft.Json.Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore,
};
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
var container = RegisterDIContainer();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
private ILifetimeScope RegisterDIContainer()
{
var container = new ContainerBuilder();
container.RegisterApiControllers(entryAssembly);
container.Register(x => new AzureBlobConfigurationProvider(x.Resolve<IBlobStorageProvider>(), "configurations", new string[] { configFileName })).As<IConfigurationProvider>().SingleInstance();
container.Register(x => new AzureBlobStorageProvider(ConfigurationManager.AppSettings["storageAccount"], ConfigurationManager.AppSettings["storageAccountKey"])).As<IBlobStorageProvider>();
container.Register(x => new TableStorageProvider(x.Resolve<IConfigurationProvider>().GetString("TableConnectionString"))).As<ITableStorageProvider>().SingleInstance();
container.Register(x => new BlobStorageMessageProvider(x.Resolve<IBlobStorageProvider>(), "configurations", new string[] { messageFileName })).As<IMessageProvider>().SingleInstance();
container.Register(x => new AzureQueueStorageProvider(x.Resolve<IConfigurationProvider>().GetString("QueueConnectionString"))).As<IQueueProvider>().SingleInstance();
container.Register(x =>
{
var config = x.Resolve<IConfigurationProvider>();
return new AzureEventHubProvider(config.GetString("EventHubConnectionString"), config.GetString("EventHubName"));
}).As<IEventProvider>().SingleInstance();
container.RegisterType<EventProviderHub>().AsSelf().SingleInstance();
RegisterBotSpecificDependencies(container);
return container.Build();
}
protected virtual void RegisterBotSpecificDependencies(ContainerBuilder container)
{
//EMPTY BY DEFAULT
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.6.2.0" newVersion="4.6.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Services.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Logging" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.1.5.0" newVersion="1.1.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Connector" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder.Autofac" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Polly" publicKeyToken="c8a3ffc3f8f825cc" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.0.0" newVersion="5.8.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.Documents.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.19.0.0" newVersion="1.19.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.WindowsAzure.Storage" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.7.0.0" newVersion="8.7.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder.History" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="mssqllocaldb" />
</parameters>
</defaultConnectionFactory>
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
</configuration>

Просмотреть файл

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="4.6.2" targetFramework="net47" />
<package id="Autofac.WebApi2" version="4.1.0" targetFramework="net47" />
<package id="Bot.Builder.Extensions" version="3.9.1" targetFramework="net47" />
<package id="Chronic.Signed" version="0.3.2" targetFramework="net47" />
<package id="EntityFramework" version="6.2.0" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.Azure.DocumentDB" version="1.19.1" targetFramework="net47" />
<package id="Microsoft.Azure.KeyVault.Core" version="2.0.4" targetFramework="net47" />
<package id="Microsoft.Bot.Builder" version="3.13.0.3" targetFramework="net47" />
<package id="Microsoft.Bot.Builder.Azure" version="3.2.5" targetFramework="net47" />
<package id="Microsoft.Bot.Builder.History" version="3.13.0.3" targetFramework="net47" />
<package id="Microsoft.Bot.Connector" version="3.13.0.3" targetFramework="net47" />
<package id="Microsoft.Bot.Connector.Teams" version="0.8.0" targetFramework="net47" />
<package id="Microsoft.Data.Edm" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.Data.OData" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.Data.Services.Client" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Logging" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.4.403061554" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocols" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Tokens" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.Rest.ClientRuntime" version="2.3.10" targetFramework="net47" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net47" />
<package id="Polly-Signed" version="5.8.0" targetFramework="net47" />
<package id="System.IdentityModel.Tokens.Jwt" version="5.2.0" targetFramework="net47" />
<package id="System.Spatial" version="5.8.3" targetFramework="net47" />
<package id="WindowsAzure.Storage" version="8.7.0" targetFramework="net47" />
</packages>

Просмотреть файл

@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunitiyBot.Common.Providers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Configuration
{
[Serializable]
public class AzureBlobConfigurationProvider : IConfigurationProvider
{
private Dictionary<string, JToken> Configurations;
private DateTime LastUpdateTime;
private IBlobStorageProvider blobStorageProvider;
private string containerName;
private IEnumerable<string> configurationFilesPathes;
public AzureBlobConfigurationProvider(IBlobStorageProvider blobStorageProvider, string containerName, IEnumerable<string> configurationFilesPathes)
{
this.blobStorageProvider = blobStorageProvider;
this.containerName = containerName;
this.configurationFilesPathes = configurationFilesPathes;
}
public T GetConfiguration<T>(string key)
{
var ConfigurationsLock = new ReaderWriterLockSlim();
var UpdateLock = new ReaderWriterLockSlim();
if (String.Equals(key, null)) throw new ArgumentNullException(key, "Argument 'key' cannot be null");
// Checks if cache is updated. Refreshes if not.
if (LastUpdateTime == null || DateTime.UtcNow.Day > LastUpdateTime.Day || DateTime.UtcNow.Hour > LastUpdateTime.Hour)
{
if (UpdateLock.TryEnterWriteLock(0))
{
LoadConfigs(ConfigurationsLock).GetAwaiter().GetResult();
UpdateLock.ExitWriteLock();
}
}
// Get and return key from Configurations dictionary.
try
{
ConfigurationsLock.EnterReadLock();
if (Configurations.ContainsKey(key))
{
return Configurations[key].ToObject<T>();
}
}
catch (Exception e)
{
throw e;
}
finally
{
ConfigurationsLock.ExitReadLock();
}
return default(T);
}
public string GetString(string key)
{
return GetConfiguration<string>(key);
}
/// <summary>
/// Loads configs from json files in blob storage to Configurations dictionary
/// </summary>
private async Task LoadConfigs(ReaderWriterLockSlim ConfigurationsLock)
{
Dictionary<string, JToken> newConfigurations = new Dictionary<string, JToken>();
foreach (var file in configurationFilesPathes)
{
var content = await blobStorageProvider.GetFileContentsAsync(containerName, file);
if (content != null)
{
var configs = JsonConvert.DeserializeObject<Dictionary<string, JToken>>(content);
foreach (var config in configs)
{
if (!newConfigurations.ContainsKey(config.Key))
{
newConfigurations.Add(config.Key, config.Value);
}
else
{
System.Diagnostics.Trace.TraceWarning($"Failed adding key {config.Key} from file {file}. It already exists.");
}
}
}
}
UpdateConfigs(newConfigurations,ConfigurationsLock);
}
private void UpdateConfigs(Dictionary<string, JToken> newConfigurations, ReaderWriterLockSlim ConfigurationsLock)
{
if (newConfigurations == null) throw new ArgumentNullException(nameof(newConfigurations));
if (newConfigurations != null && newConfigurations.Count > 0)
{
try
{
ConfigurationsLock.EnterWriteLock();
Configurations = newConfigurations;
LastUpdateTime = DateTime.UtcNow;
}
catch (Exception e)
{
System.Diagnostics.Trace.TraceWarning(e.Message);
}
finally
{
ConfigurationsLock.ExitWriteLock();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
namespace ModernApps.CommunitiyBot.Common.Configuration
{
/// <summary>
/// Generic interface for getting Configurations
/// </summary>
public interface IConfigurationProvider
{
/// <summary>
/// Gets the configuration.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key.</param>
/// <returns></returns>
T GetConfiguration<T>(string key);
string GetString(string key);
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Providers
{
[Serializable]
public class AzureBlobStorageProvider : IBlobStorageProvider
{
private string storageAccountName;
private string storageAccountKey;
public AzureBlobStorageProvider(string storageAccountName, string storageAccountKey)
{
this.storageAccountName = storageAccountName;
this.storageAccountKey = storageAccountKey;
}
public Task<bool> ExistsFileAsync(string containerName, string filePath)
{
CloudBlobContainer container = GetContainerReference(containerName);
return container.GetBlobReference(filePath).ExistsAsync();
}
public async Task<string> GenerateSASUriAsync(string containerName, string filePath, int linkExpiracy)
{
CloudBlobContainer container = GetContainerReference(containerName);
var blob = container.GetBlobReference(filePath);
if (await blob.ExistsAsync())
{
var policy = new SharedAccessBlobPolicy();
policy.SharedAccessStartTime = DateTimeOffset.UtcNow.AddMinutes(-5);
policy.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddMinutes(linkExpiracy);
policy.Permissions = SharedAccessBlobPermissions.Read;
return blob.Uri + blob.GetSharedAccessSignature(policy);
}
return string.Empty;
}
public async Task<string> GetFileContentsAsync(string containerName, string filePath)
{
CloudBlobContainer container = GetContainerReference(containerName);
var blob = container.GetBlobReference(filePath);
var reader = new StreamReader(blob.OpenRead());
return await reader.ReadToEndAsync();
}
public async Task WriteFileContentsAsync(string containerName, string fileName, string fileContent)
{
CloudBlobContainer container = GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(fileName);
await blob.UploadTextAsync(fileContent);
}
private CloudBlobContainer GetContainerReference(string containerName)
{
StorageCredentials credentials = new StorageCredentials(storageAccountName, storageAccountKey);
CloudStorageAccount account = new CloudStorageAccount(credentials, true);
var client = new CloudBlobClient(account.BlobEndpoint, account.Credentials);
CloudBlobContainer container = client.GetContainerReference(containerName);
return container;
}
}
}

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.DataProviders.AzureStorage
{
[Serializable]
public class AzureQueueStorageProvider : IQueueProvider
{
private string connectionString;
public AzureQueueStorageProvider(string connectionString)
{
this.connectionString = connectionString;
}
public async Task<T> DequeueAsync<T>(string queueName)
{
var cloudStorageAccount = CloudStorageAccount.Parse(connectionString);
var queueClient = cloudStorageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference(queueName);
if (!queue.Exists())
{
return default(T);
}
else
{
var message = await queue.GetMessageAsync();
if (message != null)
{
var objectToReturn = JsonConvert.DeserializeObject<T>(message.AsString);
await queue.DeleteMessageAsync(message);
if (queue.ApproximateMessageCount == 0)
{
await queue.DeleteIfExistsAsync();
}
return objectToReturn;
}
return default(T);
}
}
public async Task InsertInQueueAsync<T>(string queueName, T objectToInsert)
{
var cloudStorageAccount = CloudStorageAccount.Parse(connectionString);
var queueClient = cloudStorageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference(queueName);
await queue.CreateIfNotExistsAsync();
var message = new CloudQueueMessage(JsonConvert.SerializeObject(objectToInsert));
await queue.AddMessageAsync(message);
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Providers
{
public interface IBlobStorageProvider
{
Task<string> GetFileContentsAsync(string containterName, string filePath);
Task<bool> ExistsFileAsync(string containerName, string filePath);
Task<string> GenerateSASUriAsync(string containerName, string filePath, int linkExpiracy);
Task WriteFileContentsAsync(string containerName, string fileName, string fileContent);
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.DataProviders.AzureStorage
{
public interface IQueueProvider
{
Task InsertInQueueAsync<T>(string queueName, T objectToInsert);
Task<T> DequeueAsync<T>(string queueName);
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ModernApps.CommunityBot.Common.Entities;
namespace ModernApps.CommunityBot.Common.DataProviders.AzureStorage
{
public interface ITableStorageProvider
{
Task<IEnumerable<T>> RetrievePartitionFromTableAsync<T>(string tableName, string partitionKey) where T : class, ITableEntity, new();
Task<bool> SendToTableAsync<T>(string tableName, T entity) where T : class, ITableEntity, new();
Task<bool> SendToTableIfExistsAsync<T>(string tableName, T entity) where T : class, ITableEntity, new();
Task<IEnumerable<T>> RetrieveTableAsync<T>(string tableName) where T : class, ITableEntity, new();
Task DeletePartitionAsync<T>(string tableName, string partitionKey) where T : class, ITableEntity, new();
Task<T> RetrieveFromTableAsync<T>(string tableName, string partitionKey, string rowKey) where T : class, ITableEntity, new();
Task<IEnumerable<T>> RetrieveFromTableAsync<T>(string tableName, TableQuery<T> query) where T : class, ITableEntity, new();
Task DeleteFromTableAsync<T>(string tableName, IEnumerable<T> entitiesToDelete) where T : class, ITableEntity, new();
Task CreateIfNotExistsAsync<T>(string tableName, T userEntity) where T : class, ITableEntity, new();
}
}

Просмотреть файл

@ -0,0 +1,175 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.DataProviders.AzureStorage
{
[Serializable]
public class TableStorageProvider : ITableStorageProvider
{
private CloudTableClient tableClient;
public TableStorageProvider(string connectionString)
{
var storageAccount = CloudStorageAccount.Parse(connectionString);
tableClient = storageAccount.CreateCloudTableClient();
}
#region CREATE
public async Task CreateIfNotExistsAsync<T>(string tableName, T userEntity) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (RetrieveFromTableAsync<T>(tableName, userEntity.PartitionKey, userEntity.RowKey) == default(T))
{
await SendToTableAsync(tableName, userEntity);
}
}
public async Task<bool> SendToTableAsync<T>(string tableName, T entity) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
await table.CreateIfNotExistsAsync();
if (table != null)
{
TableOperation retrieveOperation = TableOperation.InsertOrReplace(entity);
var response = await table.ExecuteAsync(retrieveOperation);
if (response != null)
{
return response.HttpStatusCode >= 200 && response.HttpStatusCode < 300;
}
}
return false;
}
public async Task<bool> SendToTableIfExistsAsync<T>(string tableName, T entity) where T : class, ITableEntity, new()
{
var contents = RetrieveFromTableAsync<T>(tableName, entity.PartitionKey, entity.RowKey);
if (contents != default(T))
{
return true;
}
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null)
{
TableOperation retrieveOperation = TableOperation.InsertOrReplace(entity);
var response = await table.ExecuteAsync(retrieveOperation);
if (response != null)
{
return response.HttpStatusCode >= 200 && response.HttpStatusCode < 300;
}
}
return false;
}
#endregion
#region READ
public async Task<T> RetrieveFromTableAsync<T>(string tableName, string partitionKey, string rowKey) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null)
{
TableOperation retrieveOperation = TableOperation.Retrieve<T>(partitionKey, rowKey);
var response = await table.ExecuteAsync(retrieveOperation);
if (response != null && response.Result != null && response.Result is T)
{
return (T)response.Result;
}
}
return default(T);
}
public async Task<IEnumerable<T>> RetrievePartitionFromTableAsync<T>(string tableName, string partitionKey) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null && await table.ExistsAsync())
{
TableQuery<T> query = new TableQuery<T>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));
var response = table.ExecuteQuery(query);
if (response != null)
{
return response;
}
}
return new List<T>();
}
public async Task<IEnumerable<T>> RetrieveTableAsync<T>(string tableName) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null && await table.ExistsAsync())
{
TableQuery<T> query = new TableQuery<T>();
var response = table.ExecuteQuery<T>(query);
if (response != null)
{
return response;
}
}
return new List<T>();
}
public async Task<IEnumerable<T>> RetrieveFromTableAsync<T>(string tableName, TableQuery<T> query) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null && query != null && await table.ExistsAsync())
{
var response = table.ExecuteQuery<T>(query);
if (response != null)
{
return response;
}
}
return new List<T>();
}
#endregion
#region DELETE
public async Task DeletePartitionAsync<T>(string tableName, string partitionKey) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
var partitionObjects = await RetrievePartitionFromTableAsync<T>(tableName, partitionKey);
if (table != null)
{
var batch = new TableBatchOperation();
foreach (var obj in partitionObjects)
{
batch.Delete(obj);
}
if (batch.Any())
await table.ExecuteBatchAsync(batch);
}
}
public async Task DeleteFromTableAsync<T>(string tableName, IEnumerable<T> entitiesToDelete) where T : class, ITableEntity, new()
{
CloudTable table = tableClient.GetTableReference(tableName);
if (table != null && await table.ExistsAsync())
{
foreach (var obj in entitiesToDelete)
{
TableOperation retrieveOperation = TableOperation.Delete(obj);
await table.ExecuteAsync(retrieveOperation);
}
}
}
#endregion
}
}

Двоичные данные
src/ModernApps.CommunityBot.Common/DataProviders/QnAMaker.zip Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Collections.Generic;
namespace ModernApps.CommunityBot.Common.DataProviders.QnAMaker
{
public interface IKnowledgeBase
{
Dictionary<string,string> KnowledgeBase { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunityBot.Common.DataProviders.QnAMaker;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Providers
{
public interface IQnAMakerProvider
{
Task<IQnaResponse> GetQandAResponse(string query);
Task<bool> StoreNewAnswer(string question, string answer);
Task<bool> ReplaceAnswer(string question, string answer, IQnaResponse qnaResponse);
Task<IKnowledgeBase> GetKnowledgeBase();
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Providers
{
public interface IQnaResponse
{
string Question { get; }
string Answer { get; }
double Score { get; }
bool FoundAnswer { get; }
string MatchingQuestion { get; }
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.DataProviders.QnAMaker
{
public class KnowledgeBaseEntity : IKnowledgeBase
{
public Dictionary<string, string> KnowledgeBase { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,270 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunityBot.Common.DataProviders.QnAMaker;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace ModernApps.CommunitiyBot.Common.Providers
{
[Serializable]
public class QnAMakerProvider : IQnAMakerProvider
{
private string knowledgebaseId;
private string qnamakerSubscriptionKey;
private string qnamakerManagmentKey;
private double scoreThreshold;
private Uri qnamakerUriBase;
private Uri qnamakerManagementUriBase;
public QnAMakerProvider(string qnaMakerRootUrl, string qnaMakerManagmentUri, string knowledgebaseId, string qnamakerSubscriptionKey, string qnamakerManagmentKey, double scoreThreshold)
{
this.knowledgebaseId = knowledgebaseId;
this.qnamakerSubscriptionKey = qnamakerSubscriptionKey;
this.qnamakerManagmentKey = qnamakerManagmentKey;
this.scoreThreshold = scoreThreshold;
qnamakerUriBase = new Uri(qnaMakerRootUrl);
qnamakerManagementUriBase = new Uri(qnaMakerManagmentUri);
}
public async Task<IQnaResponse> GetQandAResponse(string query)
{
var url = $"{qnamakerUriBase}/knowledgebases/{knowledgebaseId}/generateAnswer";
var result = await SendQnAMakerRequest(GetQueryObject(query), HttpMethod.Post, url);
if (result.IsSuccessStatusCode && result.Content != null)
{
var qnaResponse = JsonConvert.DeserializeObject<QandAResponse>(await result.Content.ReadAsStringAsync(), new JsonSerializerSettings()
{
Culture = CultureInfo.InvariantCulture,
Converters = new JsonConverter[] { new HtmlEncodingConverter() }
});
qnaResponse.Threshold = scoreThreshold;
qnaResponse.Question = query;
return qnaResponse;
}
return null;
}
private object GetQueryObject(string query)
{
return new
{
question = query,
};
}
public async Task<bool> StoreNewAnswer(string question, string answer)
{
var inputObject = new QnAMakerUpdateKBInput();
inputObject.Add.QnaPairs.Add(new QnAMakerUpdateKBInput.QnaDto()
{
questions = new List<string>() { question },
answer = answer
});
return await SendUpdateKbRequest(inputObject);
}
public async Task<bool> ReplaceAnswer(string question,string answer, IQnaResponse qnaResponse)
{
var url = $"{qnamakerManagementUriBase}/knowledgebases/{knowledgebaseId}/Prod/qna ";
var response = await SendQnAMakerManagementRequest(HttpMethod.Get, url);
QnaDbResponse kb = null;
if (response.IsSuccessStatusCode)
{
kb = JsonConvert.DeserializeObject<QnaDbResponse>(await response.Content.ReadAsStringAsync());
}
if (kb != null)
{
var qnaPair = kb.qnaDocuments.FirstOrDefault(x => x.answer == qnaResponse.Answer && x.questions.Contains(question));
if (qnaPair != null)
{
var inputObject = new QnAMakerUpdateKBInput();
inputObject.Update.qnaList = new List<QnAMakerUpdateKBInput.QnaUpdateDto>()
{
new QnAMakerUpdateKBInput.QnaUpdateDto()
{
id = qnaPair.id,
answer = answer,
}
};
return await SendUpdateKbRequest(inputObject);
}
}
return false;
}
private async Task<bool> SendUpdateKbRequest(QnAMakerUpdateKBInput inputObject)
{
var url = $"{qnamakerManagementUriBase}/knowledgebases/{knowledgebaseId}";
var response = await SendQnAMakerManagementRequest(inputObject, new HttpMethod("PATCH"), url);
if (response.IsSuccessStatusCode)
{
var status = JsonConvert.DeserializeObject<OperationStatus>(await response.Content.ReadAsStringAsync());
while (status.operationState != "Succeeded")
{
Thread.Sleep(500);
var statusResponse = await SendQnAMakerManagementRequest(HttpMethod.Get, $"{qnamakerManagementUriBase}/operations/{status.operationId}");
if (statusResponse.IsSuccessStatusCode)
{
var content = JsonConvert.DeserializeObject<OperationStatus>(await statusResponse.Content.ReadAsStringAsync());
status = content;
}
else
{
return false;
}
}
var publishResponse = await SendQnAMakerManagementRequest(string.Empty, HttpMethod.Post, url);
return publishResponse.IsSuccessStatusCode;
}
else
{
return false;
}
}
private async Task<HttpResponseMessage> SendQnAMakerRequest<TIn>(TIn inputObject, HttpMethod method, string url)
{
using (var client = new HttpClient())
{
var message = new HttpRequestMessage(method, url);
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("EndpointKey", qnamakerSubscriptionKey);
message.Content = new StringContent(JsonConvert.SerializeObject(inputObject), Encoding.UTF8, "application/json");
return await client.SendAsync(message);
}
}
private async Task<HttpResponseMessage> SendQnAMakerRequest(HttpMethod method, string url)
{
using (var client = new HttpClient())
{
var message = new HttpRequestMessage(method, url);
message.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("EndpointKey", qnamakerSubscriptionKey);
return await client.SendAsync(message);
}
}
private async Task<HttpResponseMessage> SendQnAMakerManagementRequest<TIn>(TIn inputObject, HttpMethod method, string url)
{
using (var client = new HttpClient())
{
var message = new HttpRequestMessage(method, url);
message.Headers.Add("Ocp-Apim-Subscription-Key", qnamakerManagmentKey);
message.Content = new StringContent(JsonConvert.SerializeObject(inputObject), Encoding.UTF8, "application/json");
return await client.SendAsync(message);
}
}
private async Task<HttpResponseMessage> SendQnAMakerManagementRequest(HttpMethod method, string url)
{
using (var client = new HttpClient())
{
var message = new HttpRequestMessage(method, url);
message.Headers.Add("Ocp-Apim-Subscription-Key", qnamakerManagmentKey);
return await client.SendAsync(message);
}
}
public async Task<IKnowledgeBase> GetKnowledgeBase()
{
var url = $"{qnamakerManagementUriBase}/knowledgebases/{knowledgebaseId}/Prod/qna ";
var response = await SendQnAMakerManagementRequest(HttpMethod.Get, url);
if (response.IsSuccessStatusCode)
{
var qnaResponse = JsonConvert.DeserializeObject<QnaDbResponse>(await response.Content.ReadAsStringAsync());
if (qnaResponse != null)
{
return ParseKnowledgeBase(qnaResponse);
}
}
return null;
}
private IKnowledgeBase ParseKnowledgeBase(QnaDbResponse qandAResponse)
{
var knowledgeBase = new Dictionary<string, string>();
foreach (var answer in qandAResponse.qnaDocuments)
{
foreach (var question in answer.questions)
{
if (!knowledgeBase.ContainsKey(question))
knowledgeBase.Add(question, answer.answer);
}
}
return new KnowledgeBaseEntity()
{
KnowledgeBase = knowledgeBase
};
}
}
public class HtmlEncodingConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(String);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return HttpUtility.HtmlDecode((string)reader.Value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteRawValue(HttpUtility.HtmlEncode((string)value));
}
}
public class QnaDocument
{
public int id { get; set; }
public string answer { get; set; }
public string source { get; set; }
public List<string> questions { get; set; }
public List<object> metadata { get; set; }
}
public class QnaDbResponse
{
public List<QnaDocument> qnaDocuments { get; set; }
}
public class OperationStatus
{
public string operationState { get; set; }
public DateTime createdTimestamp { get; set; }
public DateTime lastActionTimestamp { get; set; }
public string userId { get; set; }
public string operationId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
namespace ModernApps.CommunitiyBot.Common.Providers
{
public class QandAResponse : IQnaResponse
{
public List<AnswerObject> answers { get; set; }
public string Question { get; set; }
public double Score => answers.OrderByDescending(x => x.score).FirstOrDefault().score;
public bool FoundAnswer => Score >= Threshold;
public double Threshold { get; set; }
public string MatchingQuestion => answers.OrderByDescending(x => x.score).FirstOrDefault().questions.FirstOrDefault();
public string Answer => answers.OrderByDescending(x => x.score).FirstOrDefault().answer;
public class AnswerObject
{
[JsonProperty("Answer")]
public string answer { get; set; }
[JsonProperty("Questions")]
public List<string> questions { get; set; }
[JsonProperty("Score")]
public double score { get; set; }
}
}
}

Просмотреть файл

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ModernApps.CommunitiyBot.Common.Providers
{
public class QnAMakerUpdateKBInput
{
public QnAMakerUpdateKBInput()
{
Add = new AddObjects();
Update = new UpdateObjects();
}
[JsonProperty("add")]
public AddObjects Add { get; set; }
[JsonProperty("update")]
public UpdateObjects Update { get; set; }
public class QnaDto
{
public int id { get; set; }
public string answer { get; set; }
public List<string> questions { get; set; }
}
public class QnaUpdateDto
{
public int id { get; set; }
public string answer { get; set; }
public List<QuestionUpdateDto> questions { get; set; }
}
public class AddObjects
{
public AddObjects()
{
QnaPairs = new List<QnaDto>();
}
[JsonProperty("qnaList")]
public List<QnaDto> QnaPairs { get; set; }
[JsonProperty("Urls")]
public List<string> Urls { get; set; }
}
public class UpdateObjects
{
public UpdateObjects()
{
qnaList = new List<QnaUpdateDto>();
}
public List<QnaUpdateDto> qnaList { get; set; }
}
}
public class QuestionUpdateDto
{
public QuestionUpdateDto()
{
add = new List<string>();
delete = new List<string>();
}
List<string> add { get; set; }
List<string> delete { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
public class FeedbackEntity : TableEntity
{
public FeedbackEntity()
{
RowKey = Guid.NewGuid().ToString();
}
private FeedbackType feedbackType;
public string Answer { get; set; }
public string Question
{
get;
set;
}
public FeedbackType FeedbackType
{
get { return feedbackType; }
set
{
feedbackType = value;
PartitionKey = value.ToString();
}
}
public int FeedbackCount { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
namespace ModernApps.CommunityBot.Common.Entities
{
public enum FeedbackType
{
POSITIVE,
NEGATIVE,
NOANSWER
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
public class IssueQueueEntity : TableEntity
{
private string text;
public IssueQueueEntity()
{
RowKey = Guid.NewGuid().ToString();
}
public string Text
{
get
{
return text;
}
set { text = value; PartitionKey = text; }
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using ModernApps.CommunityBot.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
[Serializable]
public class MessageEntity : TableEntity
{
public MessageEntity()
{
Timestamp = DateTime.UtcNow;
}
private string question;
private Guid questionCorerlationId;
public string Question
{
get
{
return question;
}
set
{
question = value;
PartitionKey = MessageHelper.NormalizeString(value).GetHashCode().ToString();
}
}
public string OriginalAnswer { get; set; }
public string ExpertAnswer { get; set; }
public MessageType MessageType { get; set; }
public string OriginalQuestion
{
get;
set;
}
public Guid QuestionCorrelationId
{
get { return questionCorerlationId; }
set { questionCorerlationId = value; RowKey = value.ToString(); }
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
namespace ModernApps.CommunityBot.Common.Entities
{
[Serializable]
public enum MessageType
{
WRONGANSWER,
NOANSWER,
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
[Serializable]
public class NotificationQueueEntity
{
public string Text { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
public class StillInterestedEntity : TableEntity
{
public StillInterestedEntity()
{
}
public StillInterestedEntity(string channelId, string userId, string question)
{
ChannelId = channelId;
UserId = userId;
Question = question;
this.RowKey = $"{channelId}:{userId}";
this.PartitionKey = question;
QuestionDay = DateTime.UtcNow.ToString("dd-MM-yyyy");
}
public string Question
{
get; set;
}
public string Answer
{
get; set;
}
public string ChannelId { get; set; }
public string UserId { get; set; }
public string ConversationId { get; set; }
public string ConversationReference { get; set; }
public MessageType MessageType { get; set; }
public bool ReceivedAnswer { get; set; }
public string QuestionDay { get; }
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Entities
{
public class UserEntity : TableEntity
{
public string UserId
{
get { return RowKey; }
set { RowKey = value; }
}
public string ChannelId
{
get { return PartitionKey; }
set { PartitionKey = value; }
}
public string ConversationReference { get; set; }
public DateTime LastActiveTime { get; set; }
public bool Notifications { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.ServiceBus.Messaging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Events
{
public class AzureEventHubProvider : IEventProvider
{
private EventHubClient client;
public AzureEventHubProvider(string connectionString, string eventHubName)
{
client = EventHubClient.CreateFromConnectionString(connectionString, eventHubName);
}
public async Task SendEventAsync(IEvent eventToSend)
{
await client.SendAsync(new EventData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(eventToSend))));
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Events
{
public abstract class EventBase : IEventBase
{
public Guid EventId => Guid.NewGuid();
public DateTime TimeOfEvent => DateTime.UtcNow;
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Events
{
public class EventProviderHub
{
private IEnumerable<IEventProvider> eventProviders;
public EventProviderHub(IEnumerable<IEventProvider> eventProviders)
{
this.eventProviders = eventProviders;
}
public async Task SendEventAsync(IEvent eventToSend)
{
foreach (var eventProvider in eventProviders)
{
await eventProvider.SendEventAsync(eventToSend);
}
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
namespace ModernApps.CommunityBot.Common.Events
{
public enum EventType
{
ENDUSERQUESTION = 0,
DOWNLOADKB = 1,
GLOBALNOTIFICATION = 2,
REFRESHKB = 3,
ANSWERQUESTION = 4,
NEWQUESTION = 5,
TECHNICALERROR = 6
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
namespace ModernApps.CommunityBot.Common.Events
{
public interface IEvent : IEventBase
{
string ChannelId { get; }
string UserId { get; }
string ConversationId { get; }
EventType EventType { get; }
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
namespace ModernApps.CommunityBot.Common.Events
{
public interface IEventBase
{
Guid EventId { get; }
DateTime TimeOfEvent { get; }
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Events
{
public interface IEventProvider
{
Task SendEventAsync(IEvent eventToSend);
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Events
{
public class TechnicalErrorEvent : EventBase, IEvent
{
public string ChannelId { get; set; }
public string UserId => string.Empty;
public string ConversationId { get; set; }
public EventType EventType => EventType.TECHNICALERROR;
public string Exception { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.Common.Helpers
{
public static class MessageHelper
{
private static Regex normalizeRegex;
static MessageHelper()
{
normalizeRegex = new Regex(@"([\?\s])*([\w\s]*)([\?\s])*");
}
public static string NormalizeString(string input)
{
if (string.IsNullOrEmpty(input)) return input;
var normalizedInput = normalizeRegex.Replace(input, "$2");
var capitalCase = char.ToUpper(normalizedInput.First()) + normalizedInput.Substring(1).ToLowerInvariant();
return capitalCase.Trim();
}
}
}

Просмотреть файл

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{61D923CB-71FC-4BED-A485-78CC2D381A55}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernApps.CommunityBot.Common</RootNamespace>
<AssemblyName>ModernApps.CommunityBot.Common</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Azure.KeyVault.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Azure.KeyVault.Core.2.0.4\lib\net45\Microsoft.Azure.KeyVault.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Edm, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Edm.5.8.3\lib\net40\Microsoft.Data.Edm.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.OData.5.8.3\lib\net40\Microsoft.Data.OData.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.Services.Client, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Services.Client.5.8.3\lib\net40\Microsoft.Data.Services.Client.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ServiceBus, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAzure.ServiceBus.4.1.7\lib\net45\Microsoft.ServiceBus.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Storage, Version=8.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\WindowsAzure.Storage.8.7.0\lib\net45\Microsoft.WindowsAzure.Storage.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Spatial, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Spatial.5.8.3\lib\net40\System.Spatial.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration\AzureBlobConfigurationProvider.cs" />
<Compile Include="Configuration\IConfigurationProvider.cs" />
<Compile Include="DataProviders\AzureStorage\AzureBlobStorageProvider.cs" />
<Compile Include="DataProviders\AzureStorage\AzureQueueStorageProvider.cs" />
<Compile Include="DataProviders\AzureStorage\IBlobStorageProvider.cs" />
<Compile Include="DataProviders\AzureStorage\IQueueProvider.cs" />
<Compile Include="DataProviders\AzureStorage\ITableStorageProvider.cs" />
<Compile Include="DataProviders\AzureStorage\TableStorageProvider.cs" />
<Compile Include="DataProviders\QnAMaker\IKnowledgeBase.cs" />
<Compile Include="DataProviders\QnAMaker\IQnAProvider.cs" />
<Compile Include="DataProviders\QnAMaker\IQnAResponse.cs" />
<Compile Include="DataProviders\QnAMaker\KnowledgeBase.cs" />
<Compile Include="DataProviders\QnAMaker\QandAMakerProvider.cs" />
<Compile Include="DataProviders\QnAMaker\QandAResponse.cs" />
<Compile Include="DataProviders\QnAMaker\QnAMakerUpdateKBInput.cs" />
<Compile Include="Entities\FeedbackEntity.cs" />
<Compile Include="Entities\FeedbackType.cs" />
<Compile Include="Entities\IssueQueueEntity.cs" />
<Compile Include="Entities\NotificationQueueEntity.cs" />
<Compile Include="Entities\MessageQueueEntity.cs" />
<Compile Include="Entities\MessageType.cs" />
<Compile Include="Entities\StillInterestedEntity.cs" />
<Compile Include="Entities\UserEntity.cs" />
<Compile Include="Events\AzureEventHubProvider.cs" />
<Compile Include="Events\EventBase.cs" />
<Compile Include="Events\EventProviderHub.cs" />
<Compile Include="Events\EventType.cs" />
<Compile Include="Events\IEventBase.cs" />
<Compile Include="Events\IEvent.cs" />
<Compile Include="Events\IEventProvider.cs" />
<Compile Include="Events\TechnicalErrorEvent.cs" />
<Compile Include="Helpers\MessageHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\BlobStorageMessageProvider.cs" />
<Compile Include="Resources\IMessageProvider.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Auditing\" />
<Folder Include="Logging\" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModernApps.CommunityBot.Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModernApps.CommunityBot.Common")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("61d923cb-71fc-4bed-a485-78cc2d381a55")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunitiyBot.Common.Providers;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Resources
{
[Serializable]
public class BlobStorageMessageProvider : IMessageProvider
{
private Dictionary<string, string> messages;
private IBlobStorageProvider blobStorageProvider;
public BlobStorageMessageProvider(IBlobStorageProvider blobStorageMessageProvider, string containerName, IEnumerable<string> messageFilesPathes)
{
messages = new Dictionary<string, string>();
blobStorageProvider = blobStorageMessageProvider;
foreach (var file in messageFilesPathes)
{
var contents = blobStorageProvider.GetFileContentsAsync(containerName, file).GetAwaiter().GetResult();
var fileDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(contents);
foreach (var value in fileDictionary)
{
if(messages.ContainsKey(value.Key))
{
Trace.TraceWarning($"Messages dictionary has duplicate key {value.Key}");
messages[value.Key] = value.Value;
} else
{
messages.Add(value.Key, value.Value);
}
}
}
}
public string GetMessage(string key)
{
return messages.ContainsKey(key) ? messages[key] : string.Empty;
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModernApps.CommunitiyBot.Common.Resources
{
public interface IMessageProvider
{
string GetMessage(string key);
}
}

Просмотреть файл

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Services.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.serviceModel>
<extensions>
<!-- In this extension section we are introducing all known service bus extensions. User can remove the ones they don't need. -->
<behaviorExtensions>
<add name="connectionStatusBehavior" type="Microsoft.ServiceBus.Configuration.ConnectionStatusElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="transportClientEndpointBehavior" type="Microsoft.ServiceBus.Configuration.TransportClientEndpointBehaviorElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="serviceRegistrySettings" type="Microsoft.ServiceBus.Configuration.ServiceRegistrySettingsElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</behaviorExtensions>
<bindingElementExtensions>
<add name="netMessagingTransport" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingTransportExtensionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="tcpRelayTransport" type="Microsoft.ServiceBus.Configuration.TcpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="httpsRelayTransport" type="Microsoft.ServiceBus.Configuration.HttpsRelayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="onewayRelayTransport" type="Microsoft.ServiceBus.Configuration.RelayedOnewayTransportElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingElementExtensions>
<bindingExtensions>
<add name="basicHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.BasicHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="webHttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WebHttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="ws2007HttpRelayBinding" type="Microsoft.ServiceBus.Configuration.WS2007HttpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netTcpRelayBinding" type="Microsoft.ServiceBus.Configuration.NetTcpRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netOnewayRelayBinding" type="Microsoft.ServiceBus.Configuration.NetOnewayRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netEventRelayBinding" type="Microsoft.ServiceBus.Configuration.NetEventRelayBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="netMessagingBinding" type="Microsoft.ServiceBus.Messaging.Configuration.NetMessagingBindingCollectionElement, Microsoft.ServiceBus, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
</system.serviceModel>
<appSettings>
<!-- Service Bus specific app setings for messaging connections -->
<add key="Microsoft.ServiceBus.ConnectionString" value="Endpoint=sb://[your namespace].servicebus.windows.net;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=[your secret]" />
</appSettings>
</configuration>

Просмотреть файл

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Azure.KeyVault.Core" version="2.0.4" targetFramework="net47" />
<package id="Microsoft.Data.Edm" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.Data.OData" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.Data.Services.Client" version="5.8.3" targetFramework="net47" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net47" />
<package id="System.ComponentModel.EventBasedAsync" version="4.3.0" targetFramework="net47" />
<package id="System.Dynamic.Runtime" version="4.3.0" targetFramework="net47" />
<package id="System.Linq.Queryable" version="4.3.0" targetFramework="net47" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net47" />
<package id="System.Spatial" version="5.8.3" targetFramework="net47" />
<package id="WindowsAzure.ServiceBus" version="4.1.7" targetFramework="net47" />
<package id="WindowsAzure.Storage" version="8.7.0" targetFramework="net47" />
</packages>

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ModernApps.CommunityBot.BotCommon;
using Autofac;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunitiyBot.Common.Configuration;
using System.Configuration;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.EndUserBot.Dialogs;
using ModernApps.CommunityBot.EndUserBot.LongRunningThreads;
using System.Reflection;
using Autofac.Integration.WebApi;
using Microsoft.Bot.Builder.Luis;
using ModernApps.CommunityBot.Common.Events;
namespace ModernApps.CommunityBot.EndUserBot
{
public class WebApiConfig : WebApiConfigBase
{
public WebApiConfig(string botDataTableName, string configFileName, string messageFileName) : base(Assembly.GetExecutingAssembly(), botDataTableName, configFileName,messageFileName)
{
}
protected override void RegisterBotSpecificDependencies(ContainerBuilder container)
{
container.Register(x =>
{
var config = x.Resolve<IConfigurationProvider>();
return new QnAMakerProvider(config.GetString("qnaMakerRootUrl"),config.GetString("qnaMakerManagementUrl"), config.GetString("qnaMakerKBId"), config.GetString("qnaMakerKey"), config.GetString("qnaMakerManagementKey"),config.GetConfiguration<double>("qnaMakerScoreThreshold"));
}).As<IQnAMakerProvider>().SingleInstance();
container.Register(x =>
{
var config = x.Resolve<IConfigurationProvider>();
return new RootDialog(new ILuisService[] { new LuisService(new LuisModelAttribute(config.GetString("LuisModelId"), config.GetString("LuisSubscriptionKey"), LuisApiVersion.V2, domain: config.GetString("LuisDomain"))) },
x.Resolve<IBlobStorageProvider>());
}).As<RootDialog>();
container.RegisterType<QueueListener>().AsSelf().SingleInstance();
container.RegisterType<StillInterestedThread>().AsSelf().SingleInstance();
container.RegisterType<GlobalNotificationsThread>().AsSelf().SingleInstance();
}
}
}

Просмотреть файл

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
<InstrumentationKey><!-- insert here --></InstrumentationKey>
<TelemetryInitializers>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.HttpDependenciesParsingTelemetryInitializer, Microsoft.AI.DependencyCollector"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureWebAppRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.BuildInfoConfigComponentVersionTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.Web.WebTestTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SyntheticUserAgentTelemetryInitializer, Microsoft.AI.Web">
<!-- Extended list of bots:
search|spider|crawl|Bot|Monitor|BrowserMob|BingPreview|PagePeeker|WebThumb|URL2PNG|ZooShot|GomezA|Google SketchUp|Read Later|KTXN|KHTE|Keynote|Pingdom|AlwaysOn|zao|borg|oegp|silk|Xenu|zeal|NING|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|Java|JNLP|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|vortex|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|voyager|archiver|Icarus6j|mogimogi|Netvibes|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|wsr-agent|http client|Python-urllib|AppEngine-Google|semanticdiscovery|facebookexternalhit|web/snippet|Google-HTTP-Java-Client-->
<Filters>search|spider|crawl|Bot|Monitor|AlwaysOn</Filters>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ClientIpHeaderTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationNameTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationCorrelationTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.UserTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AuthenticatedUserIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AccountIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SessionTelemetryInitializer, Microsoft.AI.Web"/>
</TelemetryInitializers>
<TelemetryModules>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule, Microsoft.AI.DependencyCollector">
<ExcludeComponentCorrelationHttpHeadersOnDomains>
<!--
Requests to the following hostnames will not be modified by adding correlation headers.
This is only applicable if Profiler is installed via either StatusMonitor or Azure Extension.
Add entries here to exclude additional hostnames.
NOTE: this configuration will be lost upon NuGet upgrade.
-->
<Add>core.windows.net</Add>
<Add>core.chinacloudapi.cn</Add>
<Add>core.cloudapi.de</Add>
<Add>core.usgovcloudapi.net</Add>
<Add>localhost</Add>
<Add>127.0.0.1</Add>
</ExcludeComponentCorrelationHttpHeadersOnDomains>
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
<!--
Use the following syntax here to collect additional performance counters:
<Counters>
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
...
</Counters>
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
NOTE: performance counters configuration will be lost upon NuGet upgrade.
The following placeholders are supported as InstanceName:
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
??APP_CLR_PROC?? - instance name of the application CLR process for .NET counters.
-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryModule, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.DeveloperModeWithDebuggerAttachedTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnhandledExceptionTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnobservedExceptionTelemetryModule, Microsoft.AI.WindowsServer">
<!--</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.FirstChanceExceptionStatisticsTelemetryModule, Microsoft.AI.WindowsServer">-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.RequestTrackingTelemetryModule, Microsoft.AI.Web">
<Handlers>
<!--
Add entries here to filter out additional handlers:
NOTE: handler configuration will be lost upon NuGet upgrade.
-->
<Add>System.Web.Handlers.TransferRequestHandler</Add>
<Add>Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler</Add>
<Add>System.Web.StaticFileHandler</Add>
<Add>System.Web.Handlers.AssemblyResourceLoader</Add>
<Add>System.Web.Optimization.BundleHandler</Add>
<Add>System.Web.Script.Services.ScriptHandlerFactory</Add>
<Add>System.Web.Handlers.TraceHandler</Add>
<Add>System.Web.Services.Discovery.DiscoveryRequestHandler</Add>
<Add>System.Web.HttpDebugHandler</Add>
</Handlers>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AspNetDiagnosticTelemetryModule, Microsoft.AI.Web"/>
</TelemetryModules>
<TelemetryProcessors>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryProcessor, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.Extensibility.AutocollectedMetricsExtractor, Microsoft.ApplicationInsights"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<ExcludedTypes>Event</ExcludedTypes>
</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<IncludedTypes>Event</IncludedTypes>
</Add>
</TelemetryProcessors>
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
<!--
Learn more about Application Insights configuration with ApplicationInsights.config here:
http://go.microsoft.com/fwlink/?LinkID=513840
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
--></ApplicationInsights>

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.BotCommon;
using ModernApps.CommunityBot.EndUserBot.Dialogs;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using Newtonsoft.Json;
using Microsoft.Bot.Builder.ConnectorEx;
namespace ModernApps.CommunityBot.EndUserBot
{
public class MessagesController : MessagesControllerBase
{
private ITableStorageProvider tableStorageProvider;
public MessagesController(IMessageProvider messageProvider, ITableStorageProvider tableStorageProvider) : base(messageProvider)
{
this.tableStorageProvider = tableStorageProvider;
}
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
RemoveBotNameFromMessage(activity);
if (activity.Type == ActivityTypes.Message)
{
await UpdateUserTableEntity(activity);
await Conversation.SendAsync(activity, () => (RootDialog)Configuration.DependencyResolver.GetService(typeof(RootDialog)));
}
else
{
HandleSystemMessage(activity, messageProvider.GetMessage("BotWelcome"));
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task UpdateUserTableEntity(Activity activity)
{
var entity = new UserEntity()
{
ChannelId = activity.ChannelId,
UserId = activity.From.Id,
ConversationReference = JsonConvert.SerializeObject(activity.AsMessageActivity().ToConversationReference()),
Notifications = true,
LastActiveTime = DateTime.UtcNow
};
var existingEntity = await tableStorageProvider.RetrieveFromTableAsync<UserEntity>("users", entity.PartitionKey, entity.RowKey);
if (existingEntity != null)
{
entity.Notifications = existingEntity.Notifications;
}
await tableStorageProvider.SendToTableAsync("users", entity);
}
}
}

Просмотреть файл

@ -0,0 +1,331 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.BotCommon;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using System.Linq;
using Microsoft.WindowsAzure.Storage.Table;
using System.Globalization;
using ModernApps.CommunityBot.Common.Helpers;
using Microsoft.Bot.Builder.ConnectorEx;
using Newtonsoft.Json;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using ModernApps.CommunityBot.EndUserBot.Events;
using System.Web;
namespace ModernApps.CommunityBot.EndUserBot.Dialogs
{
[Serializable]
public class RootDialog : DialogBase
{
private const string INTERNALTAG = @"\[internal\]";
IBlobStorageProvider fileBlob;
public RootDialog(ILuisService[] luisService, IBlobStorageProvider fileBlob) : base(luisService)
{
this.fileBlob = fileBlob;
}
[LuisIntent("AskQuestion")]
private async Task AskQuestion(IDialogContext context, LuisResult luisResult)
{
var question = HttpUtility.HtmlDecode(context.Activity.AsMessageActivity().Text);
var qnaAnswer = await qnaMakerProvider.GetQandAResponse(question);
if (qnaAnswer.FoundAnswer)
{
await HandleQnaAnswer(context, qnaAnswer);
}
else
{
await SendEndUserQuestionEvent(context, question, string.Empty, false);
await StoreNoAnswerFeedback(context, question);
await context.PostAsync(messageProvider.GetMessage("NoAnswerOnQna"));
await SendToQuestionToQueue(context, qnaAnswer, MessageType.NOANSWER);
await SaveOnStillInterestedTable(context, qnaAnswer, MessageType.NOANSWER);
await WhatMoreCanIDo(context);
}
}
private async Task GiveQnAAnswer(IDialogContext context, IQnaResponse qnaAnswer)
{
await context.PostAsync(string.Format(messageProvider.GetMessage("FoundAnswer"), Regex.Replace(qnaAnswer.Answer, INTERNALTAG, "")));
if (await HasEnoughCorrectFeedback(qnaAnswer))
{
await SendEndUserQuestionEvent(context, qnaAnswer.Question, qnaAnswer.Answer, true);
await context.PostAsync(messageProvider.GetMessage("GladToHelp"));
await WhatMoreCanIDo(context);
}
else
{
context.ConversationData.SetValue("qnaAnswer", qnaAnswer);
PromptDialog.Confirm(context, AfterAnswerConfirmation, messageProvider.GetMessage("IsMessageCorrect"));
}
}
private async Task AfterAnswerConfirmation(IDialogContext context, IAwaitable<bool> result)
{
var answerIsCorrect = await result;
var qnaMakerResponse = context.ConversationData.GetValue<QandAResponse>("qnaAnswer");
if (answerIsCorrect)
{
await SendEndUserQuestionEvent(context, qnaMakerResponse.Question, qnaMakerResponse.Answer, true);
await StorePositiveFeedback(context, qnaMakerResponse);
await StoreOnStillInterestedTable(context, qnaMakerResponse);
await context.PostAsync(messageProvider.GetMessage("GladToHelp"));
await WhatMoreCanIDo(context);
}
else
{
await SendEndUserQuestionEvent(context, qnaMakerResponse.Question, qnaMakerResponse.Answer, false);
await context.PostAsync(messageProvider.GetMessage("SorryToNotHelpYou"));
await StoreWrongAnswerFeedback(context, qnaMakerResponse);
await SendToQuestionToQueue(context, qnaMakerResponse, MessageType.WRONGANSWER);
await SaveOnStillInterestedTable(context, qnaMakerResponse, MessageType.WRONGANSWER);
await WhatMoreCanIDo(context);
}
}
private async Task SendToQuestionToQueue(IDialogContext context, IQnaResponse qnaAnswer, MessageType type)
{
var messageToSend = new MessageEntity()
{
Question = qnaAnswer.Question,
OriginalAnswer = qnaAnswer.Answer,
MessageType = type,
OriginalQuestion = qnaAnswer.MatchingQuestion,
QuestionCorrelationId = Guid.NewGuid()
};
await queueProvider.InsertInQueueAsync("usertoexpert", messageToSend);
}
private async Task SaveOnStillInterestedTable(IDialogContext context, IQnaResponse qnaAnswer, MessageType messageType)
{
var entity = new StillInterestedEntity(context.Activity.ChannelId, context.Activity.From.Id, MessageHelper.NormalizeString(qnaAnswer.Question))
{
ConversationId = context.Activity.Conversation.Id,
Timestamp = DateTime.UtcNow,
Answer = qnaAnswer.Answer,
MessageType = messageType,
ConversationReference = JsonConvert.SerializeObject(context.Activity.AsMessageActivity().ToConversationReference())
};
var tableContent = await tableStorageProvider.SendToTableAsync("stillInterested", entity);
}
private async Task StoreOnStillInterestedTable(IDialogContext context, IQnaResponse qnaAnswer)
{
var entity = new StillInterestedEntity(context.Activity.ChannelId, context.Activity.From.Id, MessageHelper.NormalizeString(qnaAnswer.Question))
{
ConversationId = context.Activity.Conversation.Id,
Timestamp = DateTime.UtcNow,
Answer = qnaAnswer.Answer,
ReceivedAnswer = true,
ConversationReference = JsonConvert.SerializeObject(context.Activity.AsMessageActivity().ToConversationReference())
};
var tableContent = await tableStorageProvider.SendToTableAsync("stillInterested", entity);
}
#region STORE FEEDBACK
private async Task StoreWrongAnswerFeedback(IDialogContext context, IQnaResponse qnaMakerResponse)
{
var feedbackEntry = new FeedbackEntity()
{
Answer = qnaMakerResponse.Answer,
Question = qnaMakerResponse.Question,
FeedbackType = FeedbackType.NEGATIVE
};
await SentToFeedbackTable(feedbackEntry);
}
private async Task StorePositiveFeedback(IDialogContext context, IQnaResponse qnaResponse)
{
var feedbackEntry = new FeedbackEntity()
{
Answer = qnaResponse.Answer,
Question = qnaResponse.Question,
FeedbackType = FeedbackType.POSITIVE
};
await SentToFeedbackTable(feedbackEntry);
}
private async Task SentToFeedbackTable(FeedbackEntity feedbackEntry)
{
await tableStorageProvider.SendToTableAsync("feedbackTable", feedbackEntry);
}
private async Task StoreNoAnswerFeedback(IDialogContext context, string question)
{
var feedbackEntry = new FeedbackEntity()
{
Question = question,
FeedbackType = FeedbackType.NOANSWER
};
await SentToFeedbackTable(feedbackEntry);
}
#endregion
private async Task WhatMoreCanIDo(IDialogContext context)
{
await context.PostAsync(messageProvider.GetMessage("WhatMoreCanIDo"));
}
private async Task<bool> HasEnoughCorrectFeedback(IQnaResponse qnaAnswer)
{
TableQuery<FeedbackEntity> tableQuery = new TableQuery<FeedbackEntity>().Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, FeedbackType.POSITIVE.ToString()),
TableOperators.And,
TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("Answer", QueryComparisons.Equal, qnaAnswer.Answer),
TableOperators.And,
TableQuery.GenerateFilterCondition("Question", QueryComparisons.Equal, qnaAnswer.Question))));
var positiveFeedbacks = await tableStorageProvider.RetrieveFromTableAsync("feedbackTable", tableQuery);
return positiveFeedbacks.Count() >= configuration.GetConfiguration<int>("MinPositiveFeedback");
}
[LuisIntent("DownloadKnowledgeBase")]
public async Task DownloadKnowledgeBase(IDialogContext context, LuisResult result)
{
if (await fileBlob.ExistsFileAsync(configuration.GetString("fileBlobContainer"), configuration.GetString("PublicKBDownloadFile")))
{
var linkExpiracy = configuration.GetConfiguration<int>("kbLinkExpiricy");
var link = await GenerateKbLinkWithSASToken(linkExpiracy);
await SendDownloadKnowledgeBaseEvent(context, true);
await context.PostAsync(string.Format(messageProvider.GetMessage("KbLink"), link, linkExpiracy));
}
else
{
await SendDownloadKnowledgeBaseEvent(context, false);
await context.PostAsync(messageProvider.GetMessage("KBNotAvailable"));
}
}
private async Task SendDownloadKnowledgeBaseEvent(IDialogContext context, bool fileWasAvailable)
{
await eventProvider.SendEventAsync(new DownloadKnowledgeBaseEvent(context)
{
FileAvailable = fileWasAvailable,
});
}
[LuisIntent("TurnOnNotifications")]
public async Task TurnOnNotifications(IDialogContext context, LuisResult result)
{
await SetNotifications(true, context.Activity.ChannelId, context.Activity.From.Id);
await context.PostAsync(messageProvider.GetMessage("NotificationsTurnedOn"));
}
[LuisIntent("TurnOffNotifications")]
public async Task TurnOffNotifications(IDialogContext context, LuisResult result)
{
await SetNotifications(false, context.Activity.ChannelId, context.Activity.From.Id);
await context.PostAsync(messageProvider.GetMessage("NotificationsTurnedOff"));
}
[LuisIntent("SendIssue")]
public async Task SendIssue(IDialogContext context, LuisResult result)
{
var issue = result.Entities.FirstOrDefault(x => x.Type == "issue");
if (issue == null)
{
await context.PostAsync(messageProvider.GetMessage("issueNotUnderstood"));
}
else
{
var text = context.Activity.AsMessageActivity().Text.Substring(issue.StartIndex ?? 0, (issue.EndIndex ?? 0) - (issue.StartIndex ?? 0) + 1);
await context.PostAsync(string.Format(messageProvider.GetMessage("goingToSendIssue"),text));
await queueProvider.InsertInQueueAsync("issues", new IssueQueueEntity()
{
Text = text
});
}
}
private async Task SetNotifications(bool enable, string channelId, string userId)
{
var existingEntity = await tableStorageProvider.RetrieveFromTableAsync<UserEntity>("users", channelId, userId);
if (existingEntity != null)
{
existingEntity.Notifications = enable;
}
await tableStorageProvider.SendToTableAsync("users", existingEntity);
}
private async Task<string> GenerateKbLinkWithSASToken(int linkExpiracy)
{
return await fileBlob.GenerateSASUriAsync(configuration.GetString("fileBlobContainer"), configuration.GetString("PublicKBDownloadFile"), linkExpiracy);
}
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
var qnaAnswer = await qnaMakerProvider.GetQandAResponse(result.Query);
if (qnaAnswer.FoundAnswer)
{
await HandleQnaAnswer(context, qnaAnswer);
}
else
{
await context.PostAsync(messageProvider.GetMessage("DidntUnderstand"));
}
}
private async Task HandleQnaAnswer(IDialogContext context, IQnaResponse qnaAnswer)
{
if (Regex.IsMatch(qnaAnswer.Answer, GREETINGTAG, RegexOptions.IgnoreCase))
{
await SendEndUserQuestionEvent(context, qnaAnswer.Question, qnaAnswer.Answer, true);
await context.PostAsync(Regex.Replace(qnaAnswer.Answer, GREETINGTAG, string.Empty));
}
else if (Regex.IsMatch(qnaAnswer.Answer, INTERNALTAG, RegexOptions.IgnoreCase))
{
if (configuration.GetConfiguration<List<string>>("InternalChannels").Contains(context.Activity.ChannelId))
{
await GiveQnAAnswer(context, qnaAnswer);
}
else
{
await SendEndUserQuestionEvent(context, qnaAnswer.Question, qnaAnswer.Answer, true);
await context.PostAsync(messageProvider.GetMessage("Confidential"));
await WhatMoreCanIDo(context);
}
}
else
{
await GiveQnAAnswer(context, qnaAnswer);
}
}
private async Task SendEndUserQuestionEvent(IDialogContext context, string question, string answer, bool positiveFeedback)
{
await eventProvider.SendEventAsync(new EndUserQuestionEvent(context)
{
IsAnswerCorrect = positiveFeedback,
Answer = answer,
Question = question
});
}
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.BotCommon;
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace ModernApps.CommunityBot.EndUserBot.Dialogs
{
[Serializable]
public class StillInterestedDialog : DialogBase
{
private string question;
private string answer;
private MessageType messageType;
private string conversationReference;
public StillInterestedDialog(StillInterestedEntity message)
{
question = message.Question;
messageType = message.MessageType;
conversationReference = message.ConversationReference;
answer = message.Answer;
}
public override Task OnStart(IDialogContext context)
{
PromptDialog.Confirm(context, AfterAreYouStillInterested, string.Format(messageProvider.GetMessage("StillInterestedPrompt"), question));
return Task.CompletedTask;
}
private async Task AfterAreYouStillInterested(IDialogContext context, IAwaitable<bool> result)
{
var confirm = await result;
var entity = new StillInterestedEntity(context.Activity.ChannelId, context.Activity.From.Id, MessageHelper.NormalizeString(question))
{
ConversationId = context.Activity.Conversation.Id,
Timestamp = DateTime.UtcNow,
ConversationReference = conversationReference,
Answer = answer,
};
if (confirm)
{
await context.PostAsync(messageProvider.GetMessage("ResendingQuestion"));
var messageToSend = new MessageEntity()
{
Question = question,
MessageType = messageType,
OriginalAnswer = answer,
QuestionCorrelationId = Guid.NewGuid()
};
await queueProvider.InsertInQueueAsync("usertoexpert", messageToSend);
entity.ReceivedAnswer = false;
}
else
{
entity.ReceivedAnswer = true;
await context.PostAsync(messageProvider.GetMessage("ForgettingQuestion"));
}
await tableStorageProvider.SendToTableAsync("stillInterested", entity);
await context.PostAsync(messageProvider.GetMessage("WhatMoreCanIDo"));
context.Done(0);
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.BotCommon.Events;
using ModernApps.CommunityBot.Common.Events;
namespace ModernApps.CommunityBot.EndUserBot.Events
{
public class DownloadKnowledgeBaseEvent : DialogEventBase, IEvent
{
public DownloadKnowledgeBaseEvent(IDialogContext context) : base(context)
{
}
public EventType EventType => EventType.DOWNLOADKB;
public bool FileAvailable { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.BotCommon.Events;
using ModernApps.CommunityBot.Common.Events;
namespace ModernApps.CommunityBot.EndUserBot.Events
{
internal class EndUserQuestionEvent : DialogEventBase, IEvent
{
public EndUserQuestionEvent(IDialogContext context) : base(context)
{
}
public EventType EventType => EventType.ENDUSERQUESTION;
public string Question { get; set; }
public string Answer { get; set; }
public bool IsAnswerCorrect { get; set; }
}
}

Просмотреть файл

@ -0,0 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="ModernApps.CommunityBot.EndUserBot.WebApiApplication" Language="C#" %>

Просмотреть файл

@ -0,0 +1,39 @@
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.EndUserBot.LongRunningThreads;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using System.Web.Routing;
namespace ModernApps.CommunityBot.EndUserBot
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
var webApiConfig = new WebApiConfig(ConfigurationManager.AppSettings["dataTableName"] ,ConfigurationManager.AppSettings["configFileName"], ConfigurationManager.AppSettings["messageFileName"]);
GlobalConfiguration.Configure(webApiConfig.Register);
Task.Run(() =>
{
var task = (QueueListener)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(QueueListener));
task.Execute();
});
Task.Run(() =>
{
var task = (StillInterestedThread)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(StillInterestedThread));
task.Execute();
});
Task.Run(() =>
{
var task = (GlobalNotificationsThread)GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(GlobalNotificationsThread));
task.Execute();
});
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.EndUserBot.Dialogs;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.EndUserBot.LongRunningThreads
{
public class GlobalNotificationsThread
{
private IQueueProvider queueProvider;
private IMessageProvider messageProvider;
private ITableStorageProvider tableStorageProvider;
private IConfigurationProvider configurationProvider;
public GlobalNotificationsThread(IQueueProvider queueProvider, IMessageProvider messageProvider, ITableStorageProvider tableStorageProvider, IConfigurationProvider configurationProvider)
{
this.queueProvider = queueProvider;
this.tableStorageProvider = tableStorageProvider;
this.messageProvider = messageProvider;
this.configurationProvider = configurationProvider;
}
public async void Execute()
{
while (true)
{
NotificationQueueEntity message = null;
do
{
message = await queueProvider.DequeueAsync<NotificationQueueEntity>(configurationProvider.GetString("notificationsQueue"));
if (message != null)
{
var users = await tableStorageProvider.RetrieveTableAsync<UserEntity>("users");
Parallel.ForEach(users, (user) =>
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(user.ConversationReference);
var client = new ConnectorClient(new Uri(conversationReference.ServiceUrl));
var messageActivity = Activity.CreateMessageActivity();
messageActivity.Conversation = new ConversationAccount(id: conversationReference.Conversation.Id);
messageActivity.Recipient = new ChannelAccount(id: conversationReference.User.Id, name: conversationReference.User.Name);
messageActivity.From = new ChannelAccount(id: conversationReference.Bot.Id, name: conversationReference.Bot.Name);
messageActivity.ChannelId = conversationReference.ChannelId;
messageActivity.Text = string.Format(messageProvider.GetMessage("NewNotification"), message.Text);
client.Conversations.SendToConversation((Activity)messageActivity);
});
}
} while (message != null);
Thread.Sleep(TimeSpan.FromMinutes(configurationProvider.GetConfiguration<int>("NotificationPollInterval")));
}
}
}
}

Просмотреть файл

@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Connector;
using Microsoft.WindowsAzure.Storage.Table;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.Common.Events;
using Newtonsoft.Json;
using System;
using System.Diagnostics.Eventing;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace ModernApps.CommunityBot.EndUserBot.LongRunningThreads
{
public class QueueListener
{
private IQueueProvider queueProvider;
private IMessageProvider messageProvider;
private ITableStorageProvider tableStorageProvider;
private EventProviderHub eventProvider;
public QueueListener(IQueueProvider queueProvider, IMessageProvider messageProvider, ITableStorageProvider tableStorageProvider, EventProviderHub eventProvider)
{
this.queueProvider = queueProvider;
this.messageProvider = messageProvider;
this.tableStorageProvider = tableStorageProvider;
this.eventProvider = eventProvider;
}
public async void Execute()
{
while (true)
{
MessageEntity message = null;
do
{
message = await queueProvider.DequeueAsync<MessageEntity>("experttouser");
if (message != null)
{
var questionFromStillInterested = await tableStorageProvider.RetrievePartitionFromTableAsync<StillInterestedEntity>("stillinterested", message.Question);
var users = await tableStorageProvider.RetrieveTableAsync<UserEntity>("users");
if (questionFromStillInterested.Any())
{
foreach (var question in questionFromStillInterested)
{
var user = users.FirstOrDefault(x => x.UserId == question.UserId);
if (user != null)
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(user.ConversationReference);
var connector = new ConnectorClient(new Uri(conversationReference.ServiceUrl));
MicrosoftAppCredentials.TrustServiceUrl(conversationReference.ServiceUrl, DateTime.MaxValue);
var messageActivity = Activity.CreateMessageActivity();
if (!question.ReceivedAnswer)
{
question.ReceivedAnswer = true;
if (string.Compare(message.OriginalAnswer, message.ExpertAnswer, StringComparison.InvariantCultureIgnoreCase) == 0)
{
messageActivity.Text = string.Format(messageProvider.GetMessage("ReceivedOriginalAnswer"), message.Question, message.ExpertAnswer);
}
else
{
messageActivity.Text = string.Format(messageProvider.GetMessage("ReceivedAnswer"), message.Question, message.ExpertAnswer);
}
}
else if (user.Notifications)
{
messageActivity.Text = string.Format(messageProvider.GetMessage("ReceivedAnswerUpdate"), message.Question, message.ExpertAnswer);
}
else
{
//Notifications are off. We clean the entry
await tableStorageProvider.DeleteFromTableAsync("stillinterested", new StillInterestedEntity[] { question });
return;
}
messageActivity.Conversation = new ConversationAccount(id: conversationReference.Conversation.Id);
messageActivity.Recipient = new ChannelAccount(id: conversationReference.User.Id, name: conversationReference.User.Name);
messageActivity.From = new ChannelAccount(id: conversationReference.Bot.Id, name: conversationReference.Bot.Name);
messageActivity.ChannelId = conversationReference.ChannelId;
try
{
await connector.Conversations.SendToConversationAsync((Activity)messageActivity);
await tableStorageProvider.SendToTableAsync("stillinterested", question);
}
catch (Exception e)
{
await eventProvider.SendEventAsync(new TechnicalErrorEvent()
{
Exception = JsonConvert.SerializeObject(e),
ChannelId = conversationReference.ChannelId,
ConversationId = conversationReference.Conversation.Id,
});
continue;
}
}
}
}
}
} while (message != null);
Thread.Sleep(10000);
}
}
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Autofac;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Dialogs.Internals;
using Microsoft.Bot.Connector;
using Microsoft.WindowsAzure.Storage.Table;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.EndUserBot.Dialogs;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ModernApps.CommunityBot.EndUserBot.LongRunningThreads
{
public class StillInterestedThread
{
private IQueueProvider queueProvider;
private IMessageProvider messageProvider;
private ITableStorageProvider tableStorageProvider;
private IConfigurationProvider configurationProvider;
public StillInterestedThread(IQueueProvider queueProvider, IMessageProvider messageProvider, ITableStorageProvider tableStorageProvider, IConfigurationProvider configurationProvider)
{
this.queueProvider = queueProvider;
this.tableStorageProvider = tableStorageProvider;
this.messageProvider = messageProvider;
this.configurationProvider = configurationProvider;
}
public async void Execute()
{
while (true)
{
IEnumerable<StillInterestedEntity> messages = null;
do
{
var day = DateTimeOffset.UtcNow.AddDays(-configurationProvider.GetConfiguration<int>("stillInterestedTimeoutDays"));
TableQuery<StillInterestedEntity> tableQuery = new TableQuery<StillInterestedEntity>().Where(TableQuery.CombineFilters(
TableQuery.GenerateFilterConditionForDate("Timestamp", QueryComparisons.LessThanOrEqual, day),
TableOperators.And, TableQuery.GenerateFilterConditionForBool("ReceivedAnswer",QueryComparisons.Equal,false)));
messages = await tableStorageProvider.RetrieveFromTableAsync("stillInterested",tableQuery);
if (messages.Any(x=>!x.ReceivedAnswer))
{
foreach (var message in messages.Where(x=>!x.ReceivedAnswer))
{
var conversationReference = JsonConvert.DeserializeObject<ConversationReference>(message.ConversationReference).GetPostToBotMessage();
var client = new ConnectorClient(new Uri(conversationReference.ServiceUrl));
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, conversationReference))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
var task = scope.Resolve<IDialogTask>();
//interrupt the stack
var dialog = new StillInterestedDialog(message);
task.Call(dialog.Void<object, IMessageActivity>(), null);
await task.PollAsync(CancellationToken.None);
//flush dialog stack
await botData.FlushAsync(CancellationToken.None);
}
}
await tableStorageProvider.DeleteFromTableAsync("stillInterested", messages);
}
} while (messages.Any());
Thread.Sleep(TimeSpan.FromHours(configurationProvider.GetConfiguration<int>("stillInterestedPollIntervalHours")));
}
}
}
}

Просмотреть файл

@ -0,0 +1,248 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>
</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{A8BA1066-5695-4D71-ABB4-65E5A5E0C3D4}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernApps.CommunityBot.EndUserBot</RootNamespace>
<AssemblyName>Bot Application</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<UseIISExpress>true</UseIISExpress>
<IISExpressSSLPort />
<IISExpressAnonymousAuthentication />
<IISExpressWindowsAuthentication />
<IISExpressUseClassicPipelineMode />
<UseGlobalApplicationHostFile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
<Use64BitIISExpress />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Autofac, Version=4.6.2.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.4.6.2\lib\net45\Autofac.dll</HintPath>
</Reference>
<Reference Include="Autofac.Integration.WebApi, Version=4.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
<HintPath>..\packages\Autofac.WebApi2.4.1.0\lib\net45\Autofac.Integration.WebApi.dll</HintPath>
</Reference>
<Reference Include="Chronic, Version=0.3.2.0, Culture=neutral, PublicKeyToken=3bd1f1ef638b0d3c, processorArchitecture=MSIL">
<HintPath>..\packages\Chronic.Signed.0.3.2\lib\net40\Chronic.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.Agent.Intercept, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.4.0\lib\net45\Microsoft.AI.Agent.Intercept.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.4.1\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.4.1\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.4.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.Web, Version=2.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.Web.2.4.1\lib\net45\Microsoft.AI.Web.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AI.WindowsServer, Version=2.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.2.4.1\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ApplicationInsights, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ApplicationInsights.2.4.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
</Reference>
<Reference Include="Microsoft.AspNet.TelemetryCorrelation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.TelemetryCorrelation.1.0.0\lib\net45\Microsoft.AspNet.TelemetryCorrelation.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.3.13.0.3\lib\net46\Microsoft.Bot.Builder.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Builder.Autofac, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Builder.3.13.0.3\lib\net46\Microsoft.Bot.Builder.Autofac.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bot.Connector, Version=3.13.0.3, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bot.Connector.3.13.0.3\lib\net46\Microsoft.Bot.Connector.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Data.Edm, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.Edm.5.8.3\lib\net40\Microsoft.Data.Edm.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Data.OData, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Data.OData.5.8.3\lib\net40\Microsoft.Data.OData.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Logging, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Logging.5.2.0\lib\net451\Microsoft.IdentityModel.Logging.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocol.Extensions, Version=1.0.40306.1554, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.4.403061554\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocols, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocols.5.2.0\lib\net451\Microsoft.IdentityModel.Protocols.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Protocols.OpenIdConnect.5.2.0\lib\net451\Microsoft.IdentityModel.Protocols.OpenIdConnect.dll</HintPath>
</Reference>
<Reference Include="Microsoft.IdentityModel.Tokens, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.IdentityModel.Tokens.5.2.0\lib\net451\Microsoft.IdentityModel.Tokens.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Rest.ClientRuntime, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Rest.ClientRuntime.2.3.10\lib\net452\Microsoft.Rest.ClientRuntime.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Configuration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.WindowsAzure.ConfigurationManager.3.2.3\lib\net40\Microsoft.WindowsAzure.Configuration.dll</HintPath>
</Reference>
<Reference Include="Microsoft.WindowsAzure.Storage, Version=8.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Polly, Version=5.8.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Polly.5.8.0\lib\net45\Polly.dll</HintPath>
</Reference>
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Diagnostics.DiagnosticSource, Version=4.0.2.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll</HintPath>
</Reference>
<Reference Include="System.IdentityModel.Tokens.Jwt, Version=5.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.IdentityModel.Tokens.Jwt.5.2.0\lib\net451\System.IdentityModel.Tokens.Jwt.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Net" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Numerics" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Spatial, Version=5.8.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Spatial.5.8.3\lib\net40\System.Spatial.dll</HintPath>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.ApplicationServices" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Web.Services" />
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="bot.html" />
<Content Include="default.htm" />
<Content Include="Global.asax" />
<Content Include="Web.config">
<SubType>Designer</SubType>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\WebApiConfig.cs" />
<Compile Include="Controllers\MessagesController.cs" />
<Compile Include="Events\DownloadKnowledgeBaseEvent.cs" />
<Compile Include="Events\EndUserQuestionEvent.cs" />
<Compile Include="Dialogs\RootDialog.cs" />
<Compile Include="Dialogs\StillInterestedDialog.cs" />
<Compile Include="Global.asax.cs">
<DependentUpon>Global.asax</DependentUpon>
</Compile>
<Compile Include="LongRunningThreads\QueueListener.cs" />
<Compile Include="LongRunningThreads\GlobalNotificationsThread.cs" />
<Compile Include="LongRunningThreads\StillInterestedThread.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
<Content Include="ApplicationInsights.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<SubType>Designer</SubType>
</Content>
<None Include="Properties\PublishProfiles\azstackenduserbot - Web Deploy.pubxml" />
<None Include="Properties\PublishProfiles\azstackenduserprod - Web Deploy.pubxml" />
<None Include="Properties\PublishProfiles\EAIBotEndUser - Web Deploy.pubxml" />
<None Include="Properties\PublishProfiles\eaibotenduserbot - Web Deploy.pubxml" />
<None Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon>
</None>
<None Include="Web.Release.config">
<DependentUpon>Web.config</DependentUpon>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModernApps.CommunityBot.BotCommon\ModernApps.CommunityBot.BotCommon.csproj">
<Project>{13DFF99A-6DE1-4826-A7AD-D078DD08E2D3}</Project>
<Name>ModernApps.CommunityBot.BotCommon</Name>
</ProjectReference>
<ProjectReference Include="..\ModernApps.CommunityBot.Common\ModernApps.CommunityBot.Common.csproj">
<Project>{61D923CB-71FC-4BED-A485-78CC2D381A55}</Project>
<Name>ModernApps.CommunityBot.Common</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<PropertyGroup>
<EnableMSDeployAppOffline>true</EnableMSDeployAppOffline>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>3979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:3980/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModernApps.CommunityBot.EndUserBot")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModernApps.CommunityBot.EndUserBot")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("ac0612b7-1ece-491b-85d9-2f7b857df2b2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an attribute "name" that has a value of "MyDB".
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<system.web>
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>

Просмотреть файл

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator
finds an attribute "name" that has a value of "MyDB".
<connectionStrings>
<add name="MyDB"
connectionString="Data Source=ReleaseSQLServer;Initial Catalog=MyReleaseDB;Integrated Security=True"
xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>
-->
<appSettings>
<add key="BotId" value="eaibotEndUserBot" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="MicrosoftAppId" value="6255a800-aaf3-4ce1-ad1c-fe4e5d0e4e08" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
<add key="MicrosoftAppPassword" value="e.lM(fH&amp;$/h|9u]V" xdt:Transform="Replace" xdt:Locator="Match(key)"/>
</appSettings>
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
<!--
In the example below, the "Replace" transform will replace the entire
<customErrors> section of your web.config file.
Note that because there is only one customErrors section under the
<system.web> node, there is no need to use the "xdt:Locator" attribute.
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly" xdt:Transform="Replace">
<error statusCode="500" redirect="InternalError.htm"/>
</customErrors>
-->
</system.web>
</configuration>

Просмотреть файл

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
For more information on how to configure your ASP.NET application, please visit
http://go.microsoft.com/fwlink/?LinkId=301879
-->
<configuration>
<appSettings>
<!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
<add key="BotId" value="" />
<add key="MicrosoftAppId" value="" />
<add key="MicrosoftAppPassword" value="" />
<add key="storageAccount" value="" />
<add key="storageAccountKey" value="" />
<add key="StorageConnectionString" value="" />
<add key="dataTableName" value="enduserbotdata"/>
<add key="configFileName" value="EndUserBotConfiguration.json"/>
<add key="messageFileName" value="EndUserBotMessages.json"/>
</appSettings>
<!--
For a description of web.config changes see http://go.microsoft.com/fwlink/?LinkId=235367.
The following attributes can be set on the <httpRuntime> tag.
<system.Web>
<httpRuntime targetFramework="4.6" />
</system.Web>
-->
<system.web>
<customErrors mode="Off" />
<compilation debug="true" targetFramework="4.7" />
<httpRuntime targetFramework="4.6" />
<httpModules>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" />
</httpModules>
</system.web>
<system.webServer>
<defaultDocument>
<files>
<clear />
<add value="default.htm" />
</files>
</defaultDocument>
<modules>
<remove name="TelemetryCorrelationHttpModule" />
<add name="TelemetryCorrelationHttpModule" type="Microsoft.AspNet.TelemetryCorrelation.TelemetryCorrelationHttpModule, Microsoft.AspNet.TelemetryCorrelation" preCondition="integratedMode,managedHandler" />
<remove name="ApplicationInsightsWebTracking" />
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web" preCondition="managedHandler" />
</modules>
<validation validateIntegratedModeConfiguration="false" />
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<remove name="TRACEVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers></system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.6.2.0" newVersion="4.6.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.3.0" newVersion="5.2.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Connector" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Services.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.Edm" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.KeyVault.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.1" newVersion="4.0.2.1" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.IdentityModel.Tokens.Jwt" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Tokens" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocols" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.IdentityModel.Protocols.OpenIdConnect" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder.Autofac" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Azure.Documents.Client" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.19.0.0" newVersion="1.19.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.WindowsAzure.Storage" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.7.0.0" newVersion="8.7.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Data.OData" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.3.0" newVersion="5.8.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Bot.Builder.History" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.13.0.3" newVersion="3.13.0.3" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Polly" publicKeyToken="c8a3ffc3f8f825cc" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.8.0.0" newVersion="5.8.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

Просмотреть файл

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.js"></script>
<title></title>
<meta charset="utf-8" />
<style>
html {
background-size: 90%;
background-repeat: repeat-y;
}
.chatframe {
position: absolute;
background-color: white;
opacity: 100;
float: left;
margin-left: 0%;
margin-top: 0%;
height: 90%;
width: 90%;
border: 5px #555 solid;
}
.visible {
display: inline;
}
.invisible {
visibility: hidden
}
</style>
<link href="https://webchat.botframework.com/css/botchat.css" rel="stylesheet" />
</head>
<body style="font-family:'Segoe UI'">
<div id="botInner" class="chatframe">
</div>
<script src="https://webchat.botframework.com/scripts/botchat.js"></script>
<script>
var dl = new BotChat.DirectLine({
secret: 'XzBSVLw811I.cwA.r1Y.Sz81O2zs2aXeLFBHxgDojebX1Mwu_w5vohV3Rm8stvE',
webSocket: true
});
var user = { name: 'user' };
var botConnection = Object.assign({}, dl, {
postActivity: activity => {
if (activity.channelData) {
activity.channelData.accessToken = accessToken;
} else {
activity.channelData = { accessToken: accessToken };
}
return dl.postActivity(activity);
},
});
BotChat.App({
botConnection: botConnection,
user: user,
resize: 'detect',
locale: 'en-us'
}, document.getElementById("botInner"));
</script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body style="font-family:'Segoe UI'">
<h1>ModernApps.CommunityBot.EndUserBot</h1>
<p>Describe your bot here and your terms of use etc.</p>
<p>Visit <a href="https://www.botframework.com/">Bot Framework</a> to register your bot. When you register it, remember to set your bot's endpoint to <pre>https://<i>your_bots_hostname</i>/api/messages</pre></p>
</body>
</html>

Просмотреть файл

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Autofac" version="4.6.2" targetFramework="net47" />
<package id="Autofac.WebApi2" version="4.1.0" targetFramework="net47" />
<package id="Chronic.Signed" version="0.3.2" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights" version="2.4.0" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.4.0" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.4.1" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.4.1" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.Web" version="2.4.1" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.4.1" targetFramework="net47" />
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.4.0" targetFramework="net47" />
<package id="Microsoft.AspNet.TelemetryCorrelation" version="1.0.0" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net47" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net46" />
<package id="Microsoft.Bot.Builder" version="3.13.0.3" targetFramework="net47" />
<package id="Microsoft.Bot.Connector" version="3.13.0.3" targetFramework="net47" />
<package id="Microsoft.Data.Edm" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.Data.OData" version="5.8.3" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Logging" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.4.403061554" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocols" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Protocols.OpenIdConnect" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.IdentityModel.Tokens" version="5.2.0" targetFramework="net47" />
<package id="Microsoft.Rest.ClientRuntime" version="2.3.10" targetFramework="net47" />
<package id="Microsoft.WindowsAzure.ConfigurationManager" version="3.2.3" targetFramework="net47" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net47" />
<package id="Polly" version="5.8.0" targetFramework="net47" />
<package id="System.Diagnostics.DiagnosticSource" version="4.4.1" targetFramework="net47" />
<package id="System.IdentityModel.Tokens.Jwt" version="5.2.0" targetFramework="net47" />
<package id="System.Spatial" version="5.8.3" targetFramework="net47" />
</packages>

Просмотреть файл

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ModernApps.CommunityBot.BotCommon;
using System.Reflection;
using Autofac;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunityBot.ExpertsBot.LongRunningThreads;
using ModernApps.CommunityBot.ExpertsBot.Dialogs;
using Microsoft.Bot.Builder.Luis;
using ModernApps.CommunityBot.EndUserBot.LongRunningThreads;
namespace ModernApps.CommunityBot.ExpertsBot
{
public class WebApiConfig : WebApiConfigBase
{
public WebApiConfig(string botDataTableName, string configFileName, string messageFileName) : base(Assembly.GetExecutingAssembly(), botDataTableName, configFileName, messageFileName)
{
}
protected override void RegisterBotSpecificDependencies(ContainerBuilder container)
{
container.Register(x => new AzureBlobConfigurationProvider(x.Resolve<IBlobStorageProvider>(), "configurations", new string[] { "ExpertBotConfiguration.json" })).As<IConfigurationProvider>().SingleInstance();
container.Register(x => new BlobStorageMessageProvider(x.Resolve<IBlobStorageProvider>(), "configurations", new string[] { "ExpertBotMessages.json" })).As<IMessageProvider>().SingleInstance();
container.Register(x => new AzureQueueStorageProvider(x.Resolve<IConfigurationProvider>().GetString("QueueConnectionString"))).As<IQueueProvider>().SingleInstance();
container.Register(x =>
{
var config = x.Resolve<IConfigurationProvider>();
return new QnAMakerProvider(config.GetString("qnaMakerRootUrl"), config.GetString("qnaMakerManagementUrl"), config.GetString("qnaMakerKBId"), config.GetString("qnaMakerKey"), config.GetString("qnaMakerManagementKey"), config.GetConfiguration<double>("qnaMakerScoreThreshold"));
}).As<IQnAMakerProvider>().SingleInstance();
container.Register(x => new TableStorageProvider(x.Resolve<IConfigurationProvider>().GetString("TableConnectionString"))).As<ITableStorageProvider>().SingleInstance();
container.Register(x =>
{
var config = x.Resolve<IConfigurationProvider>();
return new RootDialog(new ILuisService[] { new LuisService(new LuisModelAttribute(config.GetString("LuisModelId"), config.GetString("LuisSubscriptionKey"), LuisApiVersion.V2, config.GetString("LuisDomain"))) },
x.Resolve<IBlobStorageProvider>(),
x.Resolve<IQueueProvider>()
);
}
).AsSelf();
container.RegisterType<QueueListener>().AsSelf().SingleInstance();
container.RegisterType<IssuesThread>().AsSelf().SingleInstance();
}
}
}

Просмотреть файл

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
<InstrumentationKey><!-- insert here--></InstrumentationKey>
<TelemetryInitializers>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.HttpDependenciesParsingTelemetryInitializer, Microsoft.AI.DependencyCollector"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.AzureWebAppRoleEnvironmentTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.BuildInfoConfigComponentVersionTelemetryInitializer, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.Web.WebTestTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SyntheticUserAgentTelemetryInitializer, Microsoft.AI.Web">
<!-- Extended list of bots:
search|spider|crawl|Bot|Monitor|BrowserMob|BingPreview|PagePeeker|WebThumb|URL2PNG|ZooShot|GomezA|Google SketchUp|Read Later|KTXN|KHTE|Keynote|Pingdom|AlwaysOn|zao|borg|oegp|silk|Xenu|zeal|NING|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|Java|JNLP|Daumoa|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|vortex|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|voyager|archiver|Icarus6j|mogimogi|Netvibes|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|wsr-agent|http client|Python-urllib|AppEngine-Google|semanticdiscovery|facebookexternalhit|web/snippet|Google-HTTP-Java-Client-->
<Filters>search|spider|crawl|Bot|Monitor|AlwaysOn</Filters>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ClientIpHeaderTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationNameTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.OperationCorrelationTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.UserTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AuthenticatedUserIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AccountIdTelemetryInitializer, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.SessionTelemetryInitializer, Microsoft.AI.Web"/>
</TelemetryInitializers>
<TelemetryModules>
<Add Type="Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule, Microsoft.AI.DependencyCollector">
<ExcludeComponentCorrelationHttpHeadersOnDomains>
<!--
Requests to the following hostnames will not be modified by adding correlation headers.
This is only applicable if Profiler is installed via either StatusMonitor or Azure Extension.
Add entries here to exclude additional hostnames.
NOTE: this configuration will be lost upon NuGet upgrade.
-->
<Add>core.windows.net</Add>
<Add>core.chinacloudapi.cn</Add>
<Add>core.cloudapi.de</Add>
<Add>core.usgovcloudapi.net</Add>
<Add>localhost</Add>
<Add>127.0.0.1</Add>
</ExcludeComponentCorrelationHttpHeadersOnDomains>
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule, Microsoft.AI.PerfCounterCollector">
<!--
Use the following syntax here to collect additional performance counters:
<Counters>
<Add PerformanceCounter="\Process(??APP_WIN32_PROC??)\Handle Count" ReportAs="Process handle count" />
...
</Counters>
PerformanceCounter must be either \CategoryName(InstanceName)\CounterName or \CategoryName\CounterName
NOTE: performance counters configuration will be lost upon NuGet upgrade.
The following placeholders are supported as InstanceName:
??APP_WIN32_PROC?? - instance name of the application process for Win32 counters.
??APP_W3SVC_PROC?? - instance name of the application IIS worker process for IIS/ASP.NET counters.
??APP_CLR_PROC?? - instance name of the application CLR process for .NET counters.
-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryModule, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.DeveloperModeWithDebuggerAttachedTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnhandledExceptionTelemetryModule, Microsoft.AI.WindowsServer"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.UnobservedExceptionTelemetryModule, Microsoft.AI.WindowsServer">
<!--</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.FirstChanceExceptionStatisticsTelemetryModule, Microsoft.AI.WindowsServer">-->
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.RequestTrackingTelemetryModule, Microsoft.AI.Web">
<Handlers>
<!--
Add entries here to filter out additional handlers:
NOTE: handler configuration will be lost upon NuGet upgrade.
-->
<Add>System.Web.Handlers.TransferRequestHandler</Add>
<Add>Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler</Add>
<Add>System.Web.StaticFileHandler</Add>
<Add>System.Web.Handlers.AssemblyResourceLoader</Add>
<Add>System.Web.Optimization.BundleHandler</Add>
<Add>System.Web.Script.Services.ScriptHandlerFactory</Add>
<Add>System.Web.Handlers.TraceHandler</Add>
<Add>System.Web.Services.Discovery.DiscoveryRequestHandler</Add>
<Add>System.Web.HttpDebugHandler</Add>
</Handlers>
</Add>
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web"/>
<Add Type="Microsoft.ApplicationInsights.Web.AspNetDiagnosticTelemetryModule, Microsoft.AI.Web"/>
</TelemetryModules>
<TelemetryProcessors>
<Add Type="Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.QuickPulse.QuickPulseTelemetryProcessor, Microsoft.AI.PerfCounterCollector"/>
<Add Type="Microsoft.ApplicationInsights.Extensibility.AutocollectedMetricsExtractor, Microsoft.ApplicationInsights"/>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<ExcludedTypes>Event</ExcludedTypes>
</Add>
<Add Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.AdaptiveSamplingTelemetryProcessor, Microsoft.AI.ServerTelemetryChannel">
<MaxTelemetryItemsPerSecond>5</MaxTelemetryItemsPerSecond>
<IncludedTypes>Event</IncludedTypes>
</Add>
</TelemetryProcessors>
<TelemetryChannel Type="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.ServerTelemetryChannel, Microsoft.AI.ServerTelemetryChannel"/>
<!--
Learn more about Application Insights configuration with ApplicationInsights.config here:
http://go.microsoft.com/fwlink/?LinkID=513840
Note: If not present, please add <InstrumentationKey>Your Key</InstrumentationKey> to the top of this file.
-->
</ApplicationInsights>

Просмотреть файл

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.BotCommon;
using ModernApps.CommunityBot.ExpertsBot.Dialogs;
using ModernApps.CommunityBot.ExpertsBot.LongRunningThreads;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Bot.Connector.Teams.Models;
namespace ModernApps.CommunityBot.ExpertsBot
{
public class MessagesController : MessagesControllerBase
{
private QueueListener listener;
public MessagesController(IMessageProvider messageProvider, QueueListener queueListener) : base(messageProvider)
{
this.listener = queueListener;
}
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
RemoveBotNameFromMessage(activity);
await SetQueueListenerForChannel(activity);
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => (RootDialog)Configuration.DependencyResolver.GetService(typeof(RootDialog)));
}
else
{
HandleSystemMessage(activity, messageProvider.GetMessage("BotWelcome"));
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private async Task SetQueueListenerForChannel(Activity activity)
{
IQueueListenerPostChannel channel = null;
switch (activity.ChannelId)
{
case "msteams":
channel = new TeamsQueueListenerChannel()
{
ServiceUrl = activity.ServiceUrl,
BotId = activity.Recipient.Id,
ChannelId = activity.ChannelId,
BotName = activity.Recipient.Name,
TeamsChannelId = activity.GetChannelData<TeamsChannelData>().Channel.Id
};
break;
default:
channel = new SkypeEmulatorListenerChannel()
{
ServiceUrl = activity.ServiceUrl,
BotId = activity.Recipient.Id,
BotName = activity.Recipient.Name,
ChannelId = activity.ChannelId,
ConversationId = activity.Conversation.Id
};
break;
}
if (listener != null)
{
await listener.AddChannel(channel);
}
}
}
}

Просмотреть файл

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunityBot.BotCommon;
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.Common.Helpers;
using ModernApps.CommunityBot.ExpertsBot.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Dialogs
{
[Serializable]
public abstract class ExpertDialogBase : DialogBase
{
public ExpertDialogBase()
{
}
public ExpertDialogBase(ILuisService[] services) : base(services)
{
}
protected async Task SendAnswerToUsers(IDialogContext context, string question, string answer)
{
var normalizedQuestion = MessageHelper.NormalizeString(question);
var messageEntity = new MessageEntity()
{
Question = normalizedQuestion,
ExpertAnswer = answer,
};
await queueProvider.InsertInQueueAsync("experttouser", messageEntity);
var partitionKey = normalizedQuestion.GetHashCode().ToString();
var questionsAnswered = await tableStorageProvider.RetrievePartitionFromTableAsync<MessageEntity>("unansweredquestions", partitionKey);
var eventToSend = new AnswerQuestionEvent(context)
{
Answer = answer,
ExpertName = context.Activity.From.Name,
Question = question,
};
if (questionsAnswered.Any())
{
foreach (var q in questionsAnswered)
{
eventToSend.QuestionCorrelationId = q.QuestionCorrelationId;
await eventProvider.SendEventAsync(eventToSend);
}
}
else
{
await eventProvider.SendEventAsync(eventToSend);
}
await tableStorageProvider.DeletePartitionAsync<MessageEntity>("unansweredquestions", partitionKey);
ClearContextData(context);
await context.PostAsync(messageProvider.GetMessage("ThankYou"));
}
protected async Task KeepNewAnswer(IDialogContext context, string question, string answer, IQnaResponse qnaResponse)
{
await context.PostAsync(messageProvider.GetMessage("KeepingNew"));
await qnaMakerProvider.ReplaceAnswer(question, answer, qnaResponse);
await SendAnswerToUsers(context, question, answer);
}
}
}

Просмотреть файл

@ -0,0 +1,367 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using ModernApps.CommunitiyBot.Common.Configuration;
using ModernApps.CommunitiyBot.Common.Resources;
using ModernApps.CommunityBot.BotCommon;
using Microsoft.Bot.Builder.Luis.Models;
using System.Linq;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunityBot.Common.DataProviders.AzureStorage;
using ModernApps.CommunityBot.Common.Entities;
using System.Collections.Generic;
using Microsoft.Bot.Builder.Luis;
using ModernApps.CommunityBot.Common.Helpers;
using System.Text;
using System.Text.RegularExpressions;
using ModernApps.CommunityBot.ExpertsBot.Events;
namespace ModernApps.CommunityBot.ExpertsBot.Dialogs
{
[Serializable]
public class RootDialog : ExpertDialogBase
{
private IBlobStorageProvider blobStorageProvider;
public RootDialog(ILuisService[] luisService,
IBlobStorageProvider blobStorageProvider,
IQueueProvider queueProvider) : base(luisService)
{
this.blobStorageProvider = blobStorageProvider;
}
[LuisIntent("HandlerWrongAnswer")]
public async Task WrongAnswer(IDialogContext context, LuisResult result)
{
var question = context.ConversationData.GetValueOrDefault("question", string.Empty);
var originalQuestion = context.ConversationData.GetValueOrDefault("originalQuestion", string.Empty);
var originalAnswer = context.ConversationData.GetValueOrDefault("originalAnswer", string.Empty);
var behavior = result.Entities.FirstOrDefault(x => x.Type == "ReplaceBehavior");
if (behavior != null)
{
context.ConversationData.SetValue("requiredBehavior", ((List<object>)behavior.Resolution.FirstOrDefault().Value).FirstOrDefault().ToString());
}
context.Call(new WrongAnswerDialog(question, originalQuestion, originalAnswer), AfterWrongAnswer);
}
private async Task AfterWrongAnswer(IDialogContext context, IAwaitable<IDialogResult> result)
{
await result;
ClearContextData(context);
context.Wait(MessageReceived);
}
[LuisIntent("AnswerQuestion")]
public async Task AnswerQuestion(IDialogContext context, LuisResult result)
{
var question = result.Entities.FirstOrDefault(x => x.Type == "question");
string questionEntity = null;
if (question != null)
{
questionEntity = question.Entity;
}
if (string.IsNullOrEmpty(questionEntity))
{
questionEntity = context.ConversationData.GetValueOrDefault("question", string.Empty);
}
var answer = result.Entities.FirstOrDefault(x => x.Type == "answer");
if (answer != null)
{
context.ConversationData.SetValue("answer", answer.Entity);
}
if (string.IsNullOrEmpty(questionEntity))
{
IEnumerable<MessageEntity> unansweredQuestions = await GetUnansweredQuestions();
await context.PostAsync(messageProvider.GetMessage("NoQuestionOrMatch"));
await GenerateUnansweredQuestions(context, unansweredQuestions);
}
else
{
await AfterChooseQuestion(context, Awaitable.FromItem(questionEntity));
}
}
private async Task<IEnumerable<MessageEntity>> GetUnansweredQuestions()
{
var unansweredQuestions = await tableStorageProvider.RetrieveTableAsync<MessageEntity>("unansweredquestions");
var messageRetention = configuration.GetConfiguration<int>("MessageRetention");
var questionsToDelete = unansweredQuestions.Where(x => (DateTime.UtcNow - x.Timestamp).TotalDays >= messageRetention);
await tableStorageProvider.DeleteFromTableAsync("unansweredquestions", questionsToDelete);
return unansweredQuestions.Where(x => (DateTime.UtcNow - x.Timestamp).TotalDays <= messageRetention);
}
private async Task AfterChooseQuestion(IDialogContext context, IAwaitable<string> result)
{
var question = await result;
context.ConversationData.SetValue("questionToAnswer", question);
if (string.IsNullOrEmpty(context.ConversationData.GetValueOrDefault("answer", string.Empty)))
{
PromptDialog.Text(context, AfterAnswer, string.Format(messageProvider.GetMessage("askForAnswer"), question));
}
else
{
await AfterAnswer(context, Awaitable.FromItem(context.ConversationData.GetValue<string>("answer")));
}
}
private async Task AfterAnswer(IDialogContext context, IAwaitable<string> result)
{
var question = context.ConversationData.GetValueOrDefault("questionToAnswer", string.Empty);
var answer = await result;
context.ConversationData.SetValue("answer", answer);
var currentQnaResponse = await qnaMakerProvider.GetQandAResponse(question);
var normalizedQuestion = MessageHelper.NormalizeString(currentQnaResponse.Question);
if (currentQnaResponse.FoundAnswer)
{
var normalizedKBQuestion = MessageHelper.NormalizeString(currentQnaResponse.MatchingQuestion);
string[] promptOptions;
if (string.Compare(normalizedQuestion, normalizedKBQuestion, StringComparison.InvariantCultureIgnoreCase) == 0)
{
promptOptions = new string[] {messageProvider.GetMessage("KeepNew"),messageProvider.GetMessage("KeepOriginal")
};
}
else
{
promptOptions = new string[] { messageProvider.GetMessage("KeepNew"), messageProvider.GetMessage("KeepBoth"), messageProvider.GetMessage("KeepOriginal") };
}
context.ConversationData.SetValue("qnaResponse", currentQnaResponse);
PromptDialog.Choice(context, AfterStoringConfirmation, promptOptions, string.Format(messageProvider.GetMessage("ConfirmNewAnswer"),
normalizedQuestion, normalizedKBQuestion, currentQnaResponse.Answer));
}
else
{
await context.PostAsync(messageProvider.GetMessage("GoingToStoreAnswer"));
await qnaMakerProvider.StoreNewAnswer(normalizedQuestion, answer);
ClearContextData(context);
await SendAnswerToUsers(context, question, answer);
}
}
private async Task AfterStoringConfirmation(IDialogContext context, IAwaitable<string> result)
{
var requiredBehavior = await result;
var answer = context.ConversationData.GetValueOrDefault("answer", string.Empty);
var question = context.ConversationData.GetValueOrDefault("questionToAnswer", string.Empty);
var qnaResponse = context.ConversationData.GetValueOrDefault<QandAResponse>("qnaResponse", null); ;
if (requiredBehavior == messageProvider.GetMessage("KeepNew"))
{
await context.PostAsync(messageProvider.GetMessage("KeepingNew"));
await qnaMakerProvider.ReplaceAnswer(question, answer, qnaResponse);
await SendAnswerToUsers(context, question, answer);
}
else if (requiredBehavior == messageProvider.GetMessage("KeepBoth"))
{
await context.PostAsync(messageProvider.GetMessage("KeepingBoth"));
await qnaMakerProvider.StoreNewAnswer(MessageHelper.NormalizeString(question), answer);
await SendAnswerToUsers(context, question, answer);
}
else
{
await context.PostAsync(messageProvider.GetMessage("KeepingOriginal"));
await SendAnswerToUsers(context, question, qnaResponse.Answer);
}
ClearContextData(context);
}
[LuisIntent("UnansweredQuestions")]
public async Task UnansweredQuestions(IDialogContext context, LuisResult result)
{
var unansweredQuestions = await GetUnansweredQuestions();
var anyUnansweredQuestions = unansweredQuestions.Any();
if (anyUnansweredQuestions)
{
await GenerateUnansweredQuestions(context, unansweredQuestions);
}
else
{
await context.PostAsync(messageProvider.GetMessage("NoUnansweredQuestions"));
}
ClearContextData(context);
context.Wait(MessageReceived);
}
private async Task GenerateUnansweredQuestions(IDialogContext context, IEnumerable<MessageEntity> unansweredQuestions)
{
if (unansweredQuestions.Any())
{
await context.PostAsync(string.Format(messageProvider.GetMessage("UnansweredQuestionsList"), configuration.GetConfiguration<int>("MessageRetention")));
var message = context.MakeMessage();
message.AttachmentLayout = AttachmentLayoutTypes.List;
List<CardAction> buttons = new List<CardAction>();
foreach (var question in unansweredQuestions.Select(x => x.Question).Distinct())
{
CardAction cardAction = new CardAction()
{
Value = string.Format(messageProvider.GetMessage("UnanweredQuestionTempalate"), question),
Title = question,
Type = "imBack"
};
buttons.Add(cardAction);
}
HeroCard card = new HeroCard()
{
Buttons = buttons
};
message.Attachments.Add(new Attachment("application/vnd.microsoft.card.hero", content: card));
await context.PostAsync(message);
}
else
{
await context.PostAsync(messageProvider.GetMessage("NoUnansweredQuestions"));
}
}
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
var qnaAnswer = await qnaMakerProvider.GetQandAResponse(result.Query);
if (qnaAnswer.FoundAnswer)
{
if (Regex.IsMatch(qnaAnswer.Answer, GREETINGTAG, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
{
await context.PostAsync(Regex.Replace(qnaAnswer.Answer, GREETINGTAG, string.Empty));
}
else
{
await context.PostAsync(qnaAnswer.Answer);
}
}
else if (context.ConversationData.ContainsKey("question"))
{
context.ConversationData.SetValue("questionToAnswer", context.ConversationData.GetValue<string>("question"));
await AfterAnswer(context, Awaitable.FromItem(result.Query));
}
else
{
await context.PostAsync(messageProvider.GetMessage("NoneIntent"));
}
}
[LuisIntent("ListQuestions")]
public async Task ListQuestions(IDialogContext context, LuisResult result)
{
var expiracy = configuration.GetConfiguration<int>("privateKbFileExpiracy");
var link = await blobStorageProvider.GenerateSASUriAsync(configuration.GetString("KbFilesContainer"), configuration.GetString("privateKbFile"), expiracy);
if (string.IsNullOrEmpty(link))
{
await GenerateKnowledgeBaseFiles();
link = await blobStorageProvider.GenerateSASUriAsync(configuration.GetString("KbFilesContainer"), configuration.GetString("privateKbFile"), expiracy);
}
var eventToSend = new KnowledgeBaseEvent()
{
ChannelId = context.Activity.ChannelId,
ConversationId = context.Activity.Conversation.Id,
EventType = Common.Events.EventType.DOWNLOADKB,
UserId = context.Activity.From.Id
};
await eventProvider.SendEventAsync(eventToSend);
await context.PostAsync(string.Format(messageProvider.GetMessage("knowledgeBaseDownload"), link, expiracy));
}
[LuisIntent("RefreshKnowledgeBaseFile")]
public async Task RefreshKnowledgeBaseFile(IDialogContext context, LuisResult result)
{
var eventToSend = new KnowledgeBaseEvent()
{
ChannelId = context.Activity.ChannelId,
ConversationId = context.Activity.Conversation.Id,
EventType = Common.Events.EventType.REFRESHKB,
UserId = context.Activity.From.Id
};
await eventProvider.SendEventAsync(eventToSend);
await GenerateKnowledgeBaseFiles();
await context.PostAsync(messageProvider.GetMessage("KnowledgeBaseRefreshed"));
}
[LuisIntent("SendGlobalNotifications")]
public async Task SendGlobalNotifications(IDialogContext context, LuisResult result)
{
var notification = result.Entities.FirstOrDefault(x => x.Type == "message");
var eventToSend = new GlobalNotificationEvent()
{
ChannelId = context.Activity.ChannelId,
ConversationId = context.Activity.Conversation.Id,
OriginalMessage = result.Query,
UserId = context.Activity.From.Id
};
if (notification == null)
{
eventToSend.Success = false;
await context.PostAsync(messageProvider.GetMessage("NotificationNotFound"));
}
else
{
var text = context.Activity.AsMessageActivity().Text.Substring(notification.StartIndex ?? 0, (notification.EndIndex ?? 0) - (notification.StartIndex ?? 0) + 1);
eventToSend.Success = true;
eventToSend.Notification = text;
await queueProvider.InsertInQueueAsync(configuration.GetString("notificationsQueue"), new NotificationQueueEntity()
{
Text = text
});
await eventProvider.SendEventAsync(eventToSend);
await context.PostAsync(string.Format(messageProvider.GetMessage("NotificationSent"), text));
}
}
private async Task GenerateKnowledgeBaseFiles()
{
var knowledgeBase = await qnaMakerProvider.GetKnowledgeBase();
if (knowledgeBase != null)
{
var publicFileSB = new StringBuilder();
var privateFileSB = new StringBuilder();
foreach (var qna in knowledgeBase.KnowledgeBase)
{
publicFileSB.AppendLine(qna.Key);
privateFileSB.AppendLine(string.Format("{0}\t{1}", qna.Key, qna.Value));
}
var containerName = configuration.GetString("KbFilesContainer");
await blobStorageProvider.WriteFileContentsAsync(containerName, configuration.GetString("publicKbFile"), publicFileSB.ToString());
await blobStorageProvider.WriteFileContentsAsync(containerName, configuration.GetString("privateKbFile"), privateFileSB.ToString());
}
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunitiyBot.Common.Providers;
using ModernApps.CommunityBot.BotCommon;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Dialogs
{
[Serializable]
public class WrongAnswerDialog : ExpertDialogBase
{
private string question;
private string originalQuestion;
private string originalAnswer;
public WrongAnswerDialog(string question, string originalQuestion, string originalAnswer) : base()
{
this.question = question;
this.originalAnswer = originalAnswer;
this.originalQuestion = originalQuestion;
}
public override async Task OnStart(IDialogContext context)
{
var requiredBehavior = context.ConversationData.GetValue<string>("requiredBehavior");
if (requiredBehavior == "KeepNew" || requiredBehavior == "KeepBoth")
{
PromptDialog.Text(context, AfterAnswer, string.Format(messageProvider.GetMessage("askForAnswer"), question));
}
else
{
await context.PostAsync(messageProvider.GetMessage("KeepingOriginal"));
await SendAnswerToUsers(context, question, originalAnswer);
context.Done(new DialogResult());
}
}
private async Task AfterAnswer(IDialogContext context, IAwaitable<string> result)
{
var answer = await result;
var requiredBehavior = context.ConversationData.GetValue<string>("requiredBehavior");
if (requiredBehavior == "KeepNew")
{
var qnaResponse = await qnaMakerProvider.GetQandAResponse(question);
await KeepNewAnswer(context, question, answer, qnaResponse);
}
else if (requiredBehavior == "KeepBoth")
{
await context.PostAsync(messageProvider.GetMessage("KeepingBoth"));
await qnaMakerProvider.StoreNewAnswer(question, answer);
await SendAnswerToUsers(context, question, answer);
}
context.Done(new DialogResult());
}
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using Microsoft.Bot.Builder.Dialogs;
using ModernApps.CommunityBot.BotCommon.Events;
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Events
{
public class AnswerQuestionEvent : DialogEventBase, IEvent
{
public AnswerQuestionEvent(IDialogContext context) : base(context)
{
}
public EventType EventType => EventType.ANSWERQUESTION;
public string ExpertName { get; set; }
public string Answer { get; set; }
public string Question { get; set; }
public Guid QuestionCorrelationId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Events
{
public class GlobalNotificationEvent : EventBase, IEvent
{
public string ChannelId { get; set; }
public string UserId { get; set; }
public string ConversationId { get; set; }
public EventType EventType => EventType.GLOBALNOTIFICATION;
public string Notification { get; set; }
public string OriginalMessage { get; set; }
public bool Success { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Events
{
public class KnowledgeBaseEvent : EventBase, IEvent
{
public string ChannelId { get; set; }
public string UserId { get; set; }
public string ConversationId { get; set; }
public EventType EventType { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.// Licensed under the MIT license.
using ModernApps.CommunityBot.Common.Entities;
using ModernApps.CommunityBot.Common.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace ModernApps.CommunityBot.ExpertsBot.Events
{
public class NewQuestionEvent : EventBase, IEvent
{
public string ChannelId {get;set;}
public string UserId => string.Empty;
public string ConversationId { get; set; }
public EventType EventType => EventType.NEWQUESTION;
public string Question { get; set; }
public string OriginalAnswer { get; set; }
public MessageType MessageType { get; set; }
public Guid QuestionCorrelationId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="ModernApps.CommunityBot.ExpertsBot.WebApiApplication" Language="C#" %>

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше