Skip to content

Commit

Permalink
Merge pull request #9 from mrsimpson/assistify_0
Browse files Browse the repository at this point in the history
Preparation for PoC Assistify.
  • Loading branch information
mrsimpson authored Mar 20, 2017
2 parents 0ebae3a + 8a71ec3 commit 5e0b416
Show file tree
Hide file tree
Showing 111 changed files with 5,962 additions and 73 deletions.
5 changes: 5 additions & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,8 @@ underscorestring:underscore.string
yasaricli:slugify
yasinuslu:blaze-meta
deepwell:bootstrap-datepicker2

dbs:common
dbs:ai
assistify:help-request
assistify:bot
5 changes: 5 additions & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ accounts-password@1.3.4
accounts-twitter@1.2.0
aldeed:simple-schema@1.5.3
allow-deny@1.0.5
assistify@0.0.1
assistify:bot@0.0.1
assistify:help-request@0.0.1
autoupdate@1.2.11
babel-compiler@6.14.1
babel-runtime@1.0.1
Expand All @@ -24,6 +27,8 @@ cfs:http-methods@0.0.32
check@1.2.4
coffeescript@1.12.0_1
dandv:caret-position@2.1.1
dbs:ai@0.0.1
dbs:common@0.0.1
ddp@1.2.5
ddp-client@1.3.3
ddp-common@1.2.8
Expand Down
Empty file.
5 changes: 5 additions & 0 deletions packages/assistify-bot/bot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Write your package code here!

// Variables exported by this module can be imported by other packages and
// applications. See bot-tests.js for an example of importing.
export const name = 'bot';
17 changes: 17 additions & 0 deletions packages/assistify-bot/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Meteor.startup(()=> {
RocketChat.settings.add('Assistify_Bot_Username', "", {
group: 'Assistify',
i18nLabel: 'Assistify_Bot_Username',
type: 'string',
section: 'Bot',
public: true
});

RocketChat.settings.add('Assistify_Bot_Automated_Response_Threshold', 50, {
group: 'Assistify',
i18nLabel: 'Assistify_Bot_Automated_Response_Threshold',
type: 'int',
section: 'Bot',
public: true
});
});
26 changes: 26 additions & 0 deletions packages/assistify-bot/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Package.describe({
name: 'assistify:bot',
version: '0.0.1',
// Brief, one-line summary of the package.
summary: 'Adds a bot which propagates AI-results',
// URL to the Git repository containing the source code for this package.
git: '',
// By default, Meteor will default to using README.md for documentation.
// To avoid submitting documentation, set this field to null.
documentation: 'README.md'
});

Package.onUse(function (api) {
api.versionsFrom('1.4.2.6');
api.use('ecmascript');
api.use('assistify:help-request');
api.mainModule('bot.js');

//Server
api.addFiles('config.js', 'server');

//hooks
api.addFiles('server/hooks/onKnowledgeProviderResult.js', 'server');

//i18n in Rocket.Chat-package (packages/rocketchat-i18n/i18n
});
67 changes: 67 additions & 0 deletions packages/assistify-bot/server/hooks/onKnowledgeProviderResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Meteor.startup(() => {
/*
Trigger a bot to reply with the most relevant result one AI has retrieved results
Do this only once in order to avoid user-frustration
*/
RocketChat.callbacks.add('afterExternalMessage', function (externalMessage) {

const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(externalMessage.rid);
if(!helpRequest || helpRequest.latestBotReply){
return;
}

let totalResults = [];

if (externalMessage.result && externalMessage.result.queryTemplates) {
externalMessage.result.queryTemplates.forEach((queryTemplate, templateIndex) => {
queryTemplate.queries.forEach((query) => {
if (query.inlineResultSupport) {
const results = _dbs.getKnowledgeAdapter().getQueryResults(externalMessage.rid, templateIndex, query.creator);
if (results) {
totalResults = totalResults.concat(
results.map((result)=> {
return {
overallScore: query.confidence * (result.score || 1),
replySuggestion: result.replySuggestion
}
})
);
}
}
});
});

if (totalResults.length > 0) {
// AI believes it can contribute to the conversation => create a bot-response
const mostRelevantResult = totalResults.reduce((best, current) => current.overallScore > best.overallScore ? current : best,
{
overallScore: 0,
replySuggestion: ""
});
const scoreThreshold = RocketChat.settings.get('Assistify_Bot_Automated_Response_Threshold');
if(mostRelevantResult.replySuggestion && (mostRelevantResult.overallScore * 1000) >= scoreThreshold ) { //multiply by 1000 to simplify configuration
const botUsername = RocketChat.settings.get('Assistify_Bot_Username');
const botUser = RocketChat.models.Users.findOneByUsername(botUsername);

if (!botUser) {
throw new Meteor.Error('Erroneous Bot-Configuration: Check username')
}
try {

const botMessage = RocketChat.sendMessage({
username: botUser.username,
_id: botUser._id
}, {msg: mostRelevantResult.replySuggestion}, {_id: externalMessage.rid});

helpRequest.latestBotReply = botMessage;
RocketChat.models.HelpRequests.registerBotResponse(helpRequest._id, botMessage);

} catch (err) {
console.error('Could not add bot help message', err);
throw new Meteor.Error(err);
}
}
}
}
}, RocketChat.callbacks.priority.LOW)
});
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.assistify-create-section {
margin-bottom: 30px;

.input-submit {
margin-top: 5px;
}
}
.help-request-context {
padding: 15px;

.title {
background-color: #ccc;
font-weight: 700;
padding: 10px;
font-size: 15px;
color: #757575
}

.context-parameters-wrapper {
border: solid;
border-width: 1px;
border-color: #CCCCCC;

.context-parameter {
margin: 0;
border: 0;
border-color: #ccc;
border-top: 1px solid #ccc;
font-size: 100%;
vertical-align: baseline;
background-color: white;
padding: 10px 15px;
font-style: italic;

.value {
font-weight: bold;
}
}
}
}
10 changes: 10 additions & 0 deletions packages/assistify-help-request/client/hooks/openAiTab.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Makes the knowledge base panel open on opening a room in which it is active
* (a request, an expertise or a livechat)
*/
RocketChat.callbacks.add('enter-room', function(subscription){
const roomOpened = RocketChat.models.Rooms.findOne({_id: subscription.rid});
if(roomOpened.t === 'r' || roomOpened.t === 'e' || roomOpened.t === 'l'){
$('.flex-tab-container:not(.opened) .flex-tab-bar .hidden .icon-lightbulb').click(); //there is no ID of the tabbar's Button which we could use so far
}
});
1 change: 1 addition & 0 deletions packages/assistify-help-request/client/lib/collections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var expertiseSearchCache = new Meteor.Collection(null);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template name="AssistifyCreateChannel">
<div class="wrapper assistify-create-section">
<h4>{{_ "Assistify" }}</h4>
{{> AssistifyCreateRequest}}
<div style="min-height: 30px"></div>
{{> AssistifyCreateExpertise}}
</div>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template name="AssistifyCreateExpertise">
{{#if hasPermission 'create-e'}}
<div class="input-line no-icon">
<span>{{_ "Expertise"}}</span>
<input type="text" id="expertise" dir="auto" placeholder="{{_ 'Enter_name_here'}}">
</div>

<div class="input-line no-icon">
<label class="color-tertiary-font-color" for="experts">{{_ "Select_users"}}</label>
{{> inputAutocomplete settings=autocompleteSettings id="experts" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}}
{{#if selectedUsers}}
<ul class="selected-users">
{{#each selectedUsers}}
<li class="background-transparent-darker">{{.}} <i
class="icon-cancel remove-expert"></i>
</li>
{{/each}}
</ul>
{{/if}}
</div>

<div class="input-submit">
<button class="button primary save-expertise">{{_ "Create" }}</button>
<button class="button cancel-expertise">{{_ "Cancel" }}</button>
</div>
{{/if}}
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import toastr from 'toastr';

Template.AssistifyCreateExpertise.helpers({
selectedUsers: function () {
const instance = Template.instance();
return instance.selectedUsers.get();
},
autocompleteSettings: function () {
return {
limit: 10,
// inputDelay: 300
rules: [
{
// @TODO maybe change this 'collection' and/or template
collection: 'UserAndRoom',
subscription: 'userAutocomplete',
field: 'username',
template: Template.userSearch,
noMatchTemplate: Template.userSearchEmpty,
matchAll: true,
filter: {
exceptions: Template.instance().selectedUsers.get()
},
selector(match) {
return {term: match};
},
sort: 'username'
}
]
};
}
});

Template.AssistifyCreateExpertise.events({
'autocompleteselect #experts'(event, instance, doc) {
instance.selectedUsers.set(instance.selectedUsers.get().concat(doc.username));

instance.selectedUserNames[doc.username] = doc.name;

event.currentTarget.value = '';
return event.currentTarget.focus();
},

'click .remove-expert'(e, instance) {
let self = this;

let users = instance().selectedUsers.get();
users = _.reject(instance().selectedUsers.get(), _id => _id === self.valueOf());

instance().selectedUsers.set(users);

return $('#experts').focus();
},


'keyup #expertise': function (e, instance) {
if (e.keyCode == 13) {
instance.$('#experts').focus();
}
},

'keydown #channel-members'(e, instance) {
if (($(e.currentTarget).val() === '') && (e.keyCode === 13)) {
return instance.$('.save-channel').click();
}
},

'click .cancel-expertise': function (event, instance) {
SideNav.closeFlex(() => {
instance.clearForm()
});
},

'click .save-expertise': function (event, instance) {
event.preventDefault();
const name = instance.find('#expertise').value.toLowerCase().trim();
instance.expertiseRoomName.set(name);

if (name) {
Meteor.call('createExpertise', name, instance.selectedUsers.get(), (err, result) => {
if (err) {
console.log(err);
switch (err.error) {
case 'error-invalid-name':
toastr.error(TAPi18n.__('Invalid_room_name', name));
return;
case 'error-duplicate-channel-name':
toastr.error(TAPi18n.__('Duplicate_channel_name', name));
return;
case 'error-archived-duplicate-name':
toastr.error(TAPi18n.__('Duplicate_archived_channel_name', name));
return;
case 'error-no-members':
toastr.error(TAPi18n.__('Expertise_needs_experts', name));
return;
default:
return handleError(err)
}
}

// we're done, so close side navigation and navigate to created request-channel
SideNav.closeFlex(() => {
instance.clearForm()
});
RocketChat.callbacks.run('aftercreateCombined', {_id: result.rid, name: name});
FlowRouter.go('expertise', {name: name}, FlowRouter.current().queryParams);
});
} else {
console.log(err);
toastr.error(TAPi18n.__('The_field_is_required', TAPi18n.__('expertise')));
}
}
});

Template.AssistifyCreateExpertise.onCreated(function () {
const instance = this;
instance.expertiseRoomName = new ReactiveVar('');
instance.selectedUsers = new ReactiveVar([]);
instance.selectedUserNames = {};

instance.clearForm = function () {
instance.expertiseRoomName.set('');
instance.selectedUsers.set([]);
instance.find('#expertise').value = '';
instance.find('#experts').value = '';
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template name="AssistifyCreateRequest">
<label class="color-tertiary-font-color" for="expertise-search">{{_ "New_request_for_expertise"}}</label>
{{> inputAutocomplete settings=autocompleteExpertiseSettings id="expertise-search" class="search" autocomplete=off value=expertise}}
<!--Placeholder: Seperate field to enter any name-->
<!--<div class="input-line no-icon">-->
<!--<span>{{_ "Request"}}</span>-->
<!--<input type="text" id="request-name" class="required" dir="auto" placeholder="{{_ 'Enter_name_here'}}">-->
<!--</div>-->
<div class="input-submit">
<button class="button primary save-request">{{_ "Create" }}</button>
<button class="button cancel-request">{{_ "Cancel" }}</button>
</div>
</template>
Loading

0 comments on commit 5e0b416

Please sign in to comment.