From 45ded30ccec55d7f9f77be93883f6fb633612c85 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Wed, 17 Jan 2018 11:51:59 +0100 Subject: [PATCH 01/26] SmartiWidget: Implement new API and new UI --- .meteor/versions | 2 +- packages/assistify-ai/client/public/bulb.png | Bin 0 -> 1540 bytes .../client/public/stylesheets/smarti.css | 81 ++++++++++++++- .../client/views/app/tabbar/smarti.html | 35 +++++++ .../client/views/app/tabbar/smarti.js | 10 +- packages/assistify-ai/package.js | 6 +- packages/assistify-ai/server/SmartiProxy.js | 3 +- .../assistify-ai/server/lib/SmartiAdapter.js | 93 +++++++++++++++--- .../server/methods/SmartiWidgetBackend.js | 27 +++-- 9 files changed, 210 insertions(+), 47 deletions(-) create mode 100644 packages/assistify-ai/client/public/bulb.png diff --git a/.meteor/versions b/.meteor/versions index d3d087b3d9b5..275b8b7735aa 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -8,7 +8,7 @@ accounts-password@1.4.0 accounts-twitter@1.3.0 aldeed:simple-schema@1.5.3 allow-deny@1.0.9 -assistify:ai@0.1.0 +assistify:ai@0.2.0 assistify:defaults@0.0.1 assistify:help-request@0.1.0 autoupdate@1.3.12 diff --git a/packages/assistify-ai/client/public/bulb.png b/packages/assistify-ai/client/public/bulb.png new file mode 100644 index 0000000000000000000000000000000000000000..7c95497de82055b1aaf5b64d04d5665df1a2118d GIT binary patch literal 1540 zcmV+f2K)JmP)Sf7-TMI1h2(e&Mv`8%a z4{#HSvLdk|L@z3h5U-0aNJzXyn^f4)Ht`x=l%f>h&$-XgYlJ>H0mX=^B>?upKnIjE-u%SUR zH;R|SQ@q~lomMfCdt#%-%pYxuVH8-_-VtJXFiyjkx?X(B%YW;5Yh1T9I#x2U^ zQTkI<)1uhm=(jt3khP)rX)^m63gtVv?O-Moei?CIV+KB5v4Od2+w;SclE zz&pkS=x+hpkjR}LLkb#hMqhchFNn1d#T1VG9ETrbe|B1zif0}g+-6LSzAA{$iD7u5%d}l~j zHYL0z`m~Tr|7v8V4DN&L7~m+&tnVkT8rNn4T#TV{7LaCW%M@ybp5 znB%kH@KJV!Gz4E4uxLwqYru!&-eN$WCE6;hRMM;OK}cTx zX0S>*?1loTWL(3lz?MfUcu03Jm5m|e-gdnZXTYxAtp}bZ^O4^R9LYuE*9(dLr*Jn3 zFA?4=1W;JQ8wZlQAL9NkyjeKkgkWv%ZulVCCuwNNa%7x|f{@xvKMi{13IE758@8HL zGp9Gc4I4o zo=SwyzT4nuYj2iD3KpgGlVN|0uq`2WE^vCshtqr%24DUn+El__5ua7Q++T;mk1M`Y zpdH&m4*RxjLAh{`3_~Np`SjV`ASDT}R?vSUfv=Q2*2{=XyEyd>H@CZ=02Zkp-q%I_ z+>kh(?Gak?jg3s?_=D9di8B%i-wXKbzRRTK;z`&XN;@ytg z?I2}8u8thNbj&aGd=!asu?c-Bm3RQ;AlKC4R8_D| z$cc#&uR(l?L9F8jc&EK5tm#n7hnRdhnJRFENfkK>-Tl+*;X)6cvjHMkuaI+Rv*IR2 zL;t>Hclvf&#tQjBI&MWSysbvezAHI7UEmb_Z zJ#&)YApU3v9VI5$qF#WV^Cclq;r&3yvn1o5So@TGzt0tmm_FE%aEBDsG&b;UbEl93 z_(;1>n1VN?^ouQRznMxP9W_u3abFWsV-;|VXT9R&Z+hCT&ut?V^JRRRUZ1xlJkDC08`OPmW2z^aOqo q;(J2EwW|7ip{=#=#9z}#PyPd@BPpz!4EMYM0000 +
+
+
+
+
+

+ Bulb-icon + {{_ "Knowledge_Base"}} +

+
+
+
+
{{loadingNotification}}
+ {{#if isLoading }} + {{> loading }} + {{/if}} +
+
+ {{#let helpRequest=helpRequestByRoom }} + {{#if helpRequest}} + {{> HelpRequestActions helpRequest }} + {{/if}} + {{/let}} + {{#if isLivechat}} + {{> HelpRequestActions liveChatActions}} + {{/if}} +
+
+
+
+ + + \ No newline at end of file diff --git a/packages/assistify-ai/client/views/app/tabbar/smarti.js b/packages/assistify-ai/client/views/app/tabbar/smarti.js index ac7fa6c52a23..c5fee1513c21 100644 --- a/packages/assistify-ai/client/views/app/tabbar/smarti.js +++ b/packages/assistify-ai/client/views/app/tabbar/smarti.js @@ -33,9 +33,9 @@ Template.AssistifySmarti.onRendered(function() { const instance = this; /* in order to avoid duplicated scrollbars, have the outer one hidden */ - const parentContainer = this.$(':parent').parent(); - parentContainer.css('overflow-y', 'initial'); - this.$('.smarti-widget').css('overflow-y', 'auto'); + //const parentContainer = this.$(':parent').parent(); + //parentContainer.css('overflow-y', 'initial'); + //this.$('.smarti-widget').css('overflow-y', 'auto'); function createSmarti() { if (window.SmartiWidget === undefined) { @@ -60,10 +60,10 @@ Template.AssistifySmarti.onRendered(function() { type: WIDGET_POSTING_TYPE, cssInputSelector: '.rc-message-box .js-input-message' }, - lang: 'de' + lang: localStorage.getItem('userLanguage') }; console.debug('Initializing Smarti with options: ', JSON.stringify(smartiOptions, null, 2)); - instance.smarti = new window.SmartiWidget(instance.find('.smarti-widget'), smartiOptions); + instance.smarti = new window.SmartiWidget(instance.find('.smarti #widgetContainer'), smartiOptions); } } diff --git a/packages/assistify-ai/package.js b/packages/assistify-ai/package.js index b3788ee57ce1..ea1d5ded0202 100755 --- a/packages/assistify-ai/package.js +++ b/packages/assistify-ai/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'assistify:ai', - version: '0.1.0', + version: '0.2.0', summary: 'Integration of artificial knowledge', git: 'http://github.com/assistify/Rocket.Chat', documentation: 'README.md' @@ -49,8 +49,8 @@ Package.onUse(function(api) { api.addFiles('client/public/stylesheets/smarti.css', 'client'); //Assets - api.addAssets('client/public/assistify.png', 'client'); - api.addAssets('client/public/assistify-beta.png', 'client'); + api.addAssets('client/public/bulb.png', 'client'); + //api.addAssets('client/public/assistify-beta.png', 'client'); //i18n in Rocket.Chat-package (packages/rocketchat-i18n/i18n diff --git a/packages/assistify-ai/server/SmartiProxy.js b/packages/assistify-ai/server/SmartiProxy.js index 63eff8db4a15..3c0a7e0c4374 100644 --- a/packages/assistify-ai/server/SmartiProxy.js +++ b/packages/assistify-ai/server/SmartiProxy.js @@ -33,7 +33,6 @@ export class SmartiProxy { * @returns {Object} */ static propagateToSmarti(method, path, body = null) { - const url = `${ SmartiProxy.smartiUrl }${ path }`; const header = { 'X-Auth-Token': SmartiProxy.smartiAuthToken, @@ -41,7 +40,7 @@ export class SmartiProxy { }; try { const response = HTTP.call(method, url, {data: body, headers: header}); - if (response.statusCode === 200) { + if (response.statusCode === 200 || response.statusCode === 201) { return response.data || response.content; //.data if it's a json-response } else { SystemLogger.debug('Got unexpected result from Smarti', method, 'to', url, 'response', JSON.stringify(response)); diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index e975264885e1..82cc03ca0571 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -35,21 +35,83 @@ export class SmartiAdapter { static onMessage(message) { //TODO is this always a new one, what about update - const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); - const supportArea = helpRequest ? helpRequest.supportArea : undefined; - const requestBody = { - // TODO: Should this really be in the responsibility of the Adapter? - webhook_url: SmartiAdapter.rocketWebhookUrl, - message_id: message._id, - channel_id: message.rid, - user_id: message.u._id, - // username: message.u.username, - text: message.msg, - timestamp: message.ts, - origin: message.origin, - support_area: supportArea + + //const user = RocketChat.models.Users.findOneById(message.u._id); + const requestBodyMessage = { + "id" : message._id, + "time" : message.ts, + "origin" : "User", //user.type, + "content" : message.msg, + "user" : { + "id" : message.u._id + } + //,"private" : false }; - return SmartiProxy.propagateToSmarti(verbs.post, `rocket/${ SmartiAdapter.smartiKnowledgeDomain }`, requestBody); + + //SystemLogger.debug("Message:", requestBodyMessage); + //SystemLogger.debug("User:", user); + + const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); + let analysisResult; + // conversation exists for channel? + if(m && m.conversationId) { + SystemLogger.info("Conversation found for channel"); + // add message to conversation + SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ m.conversationId }/message`, requestBodyMessage); + // request analysis results + analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ m.conversationId }/analysis`); + } else { + SystemLogger.info("Conversation not found for channel"); + const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); + const supportArea = helpRequest ? helpRequest.supportArea : undefined; + const room = RocketChat.models.Rooms.findOneById(message.rid); + //SystemLogger.debug("HelpRequest:", helpRequest); + //SystemLogger.debug("Room:", room); + + const requestBodyConversation = { + "meta" : { + "support_area" : [supportArea], + "channel_id" : [message.rid] + }, + "user" : { + "id" : room.u._id + }, + "messages" : [requestBodyMessage], + "context" : { + "contextType" : "rocket.chat" + /* + "domain" : "test", + "environment" : { + + } + */ + } + }; + + SystemLogger.debug("Creating conversation:", JSON.stringify(requestBodyConversation, null, '\t')); + // create conversation, send message along and request analysis + analysisResult = SmartiProxy.propagateToSmarti(verbs.post, `conversation?analysis=true`, requestBodyConversation); + analysisResult = analysisResult ? analysisResult.analysis : null; + } + SystemLogger.debug(`analysisResult:`, JSON.stringify(analysisResult, null, '\t')); + + if(analysisResult && analysisResult.conversation) { + // update/insert channel/conversation specific timestamp + RocketChat.models.LivechatExternalMessage.update( + { + _id: message.rid + }, { + rid: message.rid, + knowledgeProvider: 'smarti', + conversationId: analysisResult.conversation, + ts: message.ts + }, { + upsert: true + } + ); + + RocketChat.Notifications.notifyRoom(message.rid, 'newConversationResult', analysisResult); + } } /** @@ -60,11 +122,10 @@ export class SmartiAdapter { * @returns {*} */ static onClose(room) { //async - // get conversation id const m = RocketChat.models.LivechatExternalMessage.findOneById(room._id); if (m) { - SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ m.conversationId }/publish`); + SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${m.conversationId}/meta.status`, "Complete"); } else { SystemLogger.error(`Smarti - closing room failed: No conversation id for room: ${ room._id }`); } diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index 26b04bc00cda..44618d5f2c4a 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -18,18 +18,14 @@ Meteor.methods({ * @returns {String} - the conversation Id */ getConversationId(channelId) { - - const clientDomain = RocketChat.settings.get('Assistify_AI_Smarti_Domain'); - return RocketChat.RateLimiter.limitFunction( - SmartiProxy.propagateToSmarti, 5, 1000, { - userId(userId) { - return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); - } - } - )(verbs.get, `rocket/${ clientDomain }/${ channelId }/conversationid`); - // Smarti only returns the plain id (no JSON Object), therefore we do not get an response.data obeject. - // use body instead - // TODO: Smarti release 0.7.0 should return a valid JSON + SystemLogger.debug(`Retrieving conversation ID for channel: ${ channelId }`); + const m = RocketChat.models.LivechatExternalMessage.findOneById(channelId); + if(m) { + return m.conversationId; + } else { + SystemLogger.debug(`Smarti - no conversation found for channel: ${ channelId }`); + return null; + } }, /** @@ -40,14 +36,13 @@ Meteor.methods({ * @returns {Object} - the analysed conversation */ getConversation(conversationId) { - return RocketChat.RateLimiter.limitFunction( SmartiProxy.propagateToSmarti, 5, 1000, { userId(userId) { return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `conversation/${ conversationId }`); + )(verbs.get, `conversation/${ conversationId }/analysis`); }, /** @@ -61,14 +56,14 @@ Meteor.methods({ * @returns {Object} - the suggestions */ getQueryBuilderResult(conversationId, templateIndex, creator, start) { - + // TODO new api -> client-side pagination? return RocketChat.RateLimiter.limitFunction( SmartiProxy.propagateToSmarti, 5, 1000, { userId(userId) { return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `conversation/${ conversationId }/template/${ templateIndex }/${ creator }?start=${ start }`); + )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }`); } }); From 0c04c923fad0de89d5f33509db971124fb333983 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Wed, 17 Jan 2018 14:56:14 +0100 Subject: [PATCH 02/26] SmartiWidget: optimize UI --- packages/assistify-ai/client/views/app/tabbar/smarti.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/assistify-ai/client/views/app/tabbar/smarti.html b/packages/assistify-ai/client/views/app/tabbar/smarti.html index e2cf57d95a24..0a3ae791389c 100644 --- a/packages/assistify-ai/client/views/app/tabbar/smarti.html +++ b/packages/assistify-ai/client/views/app/tabbar/smarti.html @@ -11,7 +11,9 @@

-
{{loadingNotification}}
+
+
{{loadingNotification}}
+
{{#if isLoading }} {{> loading }} {{/if}} From 02e4d3b2c214d2cfb6a08feca5928dca7c9e61c7 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Tue, 23 Jan 2018 11:17:17 +0100 Subject: [PATCH 03/26] SmartiWidget: improve styling --- packages/assistify-ai/client/public/stylesheets/smarti.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/assistify-ai/client/public/stylesheets/smarti.css b/packages/assistify-ai/client/public/stylesheets/smarti.css index c81d5028ae95..dd8ebe6dee1f 100644 --- a/packages/assistify-ai/client/public/stylesheets/smarti.css +++ b/packages/assistify-ai/client/public/stylesheets/smarti.css @@ -22,6 +22,7 @@ border-left: 1px solid #dfdfdf; background: #f4f4f4; overflow: hidden; + line-height: normal; } .smarti #widgetContainer #widgetWrapper { @@ -60,7 +61,8 @@ opacity: 1; width: 100%; text-align: center; - border-top: 1px solid #CBCBCB; } + border-top: 1px solid #CBCBCB; + transition: 0.2s; } .smarti #widgetContainer #widgetWrapper #widgetFooter .help-request-actions { margin: 0; } .smarti #widgetContainer #widgetWrapper #widgetFooter button { From b55510e1480882cf3b010578193d86bd94a2562f Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Tue, 23 Jan 2018 11:30:32 +0100 Subject: [PATCH 04/26] SmartiWidget: implement conversation ID lookup via legacy webservice --- .../server/methods/SmartiWidgetBackend.js | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index 44618d5f2c4a..05d40ed52708 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -20,11 +20,44 @@ Meteor.methods({ getConversationId(channelId) { SystemLogger.debug(`Retrieving conversation ID for channel: ${ channelId }`); const m = RocketChat.models.LivechatExternalMessage.findOneById(channelId); - if(m) { + if(m && m.conversationId) { return m.conversationId; } else { - SystemLogger.debug(`Smarti - no conversation found for channel: ${ channelId }`); - return null; + SystemLogger.debug(`Smarti - Trying legacy service to retrieve conversation ID...`); + let conversation = RocketChat.RateLimiter.limitFunction( + SmartiProxy.propagateToSmarti, 5, 1000, { + userId(userId) { + return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); + } + } + )(verbs.get, `legacy/rocket.chat?channel_id=${ channelId }`); + + if(conversation && conversation.id) { + let timestamp = conversation.messages && + conversation.messages[conversation.messages.length - 1] && + conversation.messages[conversation.messages.length - 1].time; + + if(!timestamp) timestamp = conversation.lastModified; + + // Store conversation ID and latest conversation timestamp + RocketChat.models.LivechatExternalMessage.update( + { + _id: channelId + }, { + rid: channelId, + knowledgeProvider: 'smarti', + conversationId: conversation.id, + ts: timestamp + }, { + upsert: true + } + ); + + return conversation.id; + } else { + SystemLogger.debug(`Smarti - no conversation found for channel: ${ channelId }`); + return null; + } } }, From cad1bd277fed12493dbb789ad3ebc5f20c991410 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Wed, 7 Feb 2018 17:47:08 +0100 Subject: [PATCH 05/26] SmartiWidget: UI Optimizations --- .../assistify-ai/client/public/assistify.png | Bin 1356 -> 71307 bytes .../client/public/stylesheets/smarti.css | 2 +- .../client/views/app/tabbar/smarti.html | 2 +- .../client/views/app/tabbar/smarti.js | 2 +- packages/assistify-ai/package.js | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assistify-ai/client/public/assistify.png b/packages/assistify-ai/client/public/assistify.png index ad9df93e4acb93c2843922164ee52ecaf27dc5f0..fcad91d1c737e1f5e1c29df0315901c6754d7dfc 100644 GIT binary patch literal 71307 zcmV*%KsdjNP)^whgEoEYxR-fsdWD1ritXGB2} z6Bs~6Mba+2%jTV#PW8XnJp;?I3%l!vnce=h*qQ2@4%Joft5>hyD+Uf6IB?*=K`p={ zGaNW@;J|@{>cAlo2M!!Ka8N5a1meJf0|yRj1&2T!IB?*=L9O5rhyw=>95|>I90GCR zz<~n?wSq$+4jede;GkA;2*iN{2M!$63J!rdaNxj!gId8M5C?x8j4|Mx+L9T0=%`>! z2z}!Cdw$rlCJZ6e?M!GkkL;j{!XXd`wTB=OaR`_U>$@Z&6>w@*B7UP(v3(J_P&sb> zhnxetUg0p77X>+SkyNoNnDw7s4^~~6NR&t%R()YPQ6TcAr^dvn_VD@j z8uG9M2aN@XKpfN#L>{JTX&pk#tVDU}z^+o?-ly=g@{{pcJIp^0lbeoHX-qko_F^gr zZhphu?mdB*UtQ1h@^e9!bIpA@rC?ZcRRl?7V4xh0A*DnFfe~rYN7d#BEuzRl^p-QM zzgx8Z5l1Y@3KQwU4UHq+I9iH>D278I4r&gYJh<4Y0|>{pov4gEZ9Wpm2+XrvR5fKD zQxyD{=Yh}rx&GmXca5F9Kjo#RpBO*3g6rDyHV`NW4R_bvynJ<>@`Y@-Uca3d7zNmfl>Feg+eRLKQ(Dvj`6o96uOyKm4_A`WVmb(*- z_jDLgU@>IV%JV?B##$( zAUFwh-2_RZ!esNP4d}OI_n<+%VvV^I6Z~fO{`Yu6(Jg`J7Ht95nhhdJ+i1oCyerFQ zq1hE}Z@L7LVIOMFy5n2x7F<`IZA7!gQl zii!wQq6{jFS_q^HUK7jwx>e0xy-D43@B9O6_xiaWwwV}sjf%JCz(J$IArJ>PkQahICE?7T-^ zwf0};z8sX@rcELZtc(zA;zfxd^{!G1jCLP){B7}rN9CIStJ6hK7o--^&kYEokQW)1 zX1Zi6Vnhy9H=)W4DOA>hidDuqtz!eyv+%RQO^cBe1sAB?C~^tXJ@*u@fA-$uPYz(V zi03vOL%V}$KwWiw2eAy>ra_;$H1Nb!bJy;C6xd{gAkDxoV=vX^@~K0{D&tOD%l!e< z`d$6Bq42U^yayDuNr^A(8$Q1(_{wYf_+3CHpuwc4!URF(rrAwOg(0q%J?=^vHVxTz zUOCs3NyE??-3lYk!crx|EOCe+DiB811Y*-n=rh)#FhD&VzAWjk~V2M!tm_0jnq#0qRupif-v)g7enU9Lg`IiD97KN5KI`9)@S4vKJV zY)k?jAHv{@<09auciiHltl-X6Gga+BJ+;<^z(YM__+I7O7d8qe*nPk zrs<-q0*^fPq?w;jGc-grrswJ_X0z1f=Rwo#b2JnfL%C_l%;j2KcE>+vf~wb>J%pZ$ zhwLpLCc&cQ@n^Ob6aLXs^z}Gks^Af14a-Qj$PWwt`Xvy80L%z*T~wIF;|15k8F|YW ztSsLSE+mnlLn00u3E>qp4w@6l`47p1rKkGbb1%GJXp#9D$nscTP>5=V90XMrFp<$0 z!9<$IQqCCT5i--RfuKrGfDJ3KIG6F71~-fu?ztD;irR{u`f$6|O z!^3HK%?b7}R}eDNO=-}HUEG(r)?lGynmAym&A#kJKjO}WO=TW6`~G6%V}we?xTq#I?wQQS-nFqoWRM; zo>oXjS3jW#Ml$1Okr^1bGGqFS6TMwi4ZL@%-PK;lrrUWQ8`Lpcg*@pt35osXgR!7NX36ry6wtPw(+)~6%c7?vi`Sg^GaLul7# z4Bpdln%2VfhqVwgip!{AJe)8*X}Y`1$~|Uje-D!=9tk~boe)%AgIV71TtyNYzbJju zzt8q|wG8TH6NxPAz(IY%X>82}D13#?bi;kwMe`sbejJhm`EXDy5gTL|@p_nqgPOo0kmdlg z6_}=>OrAIf;(d2OFi3{*u;xLqqvNXTZvCq*kAq=Yv98D~T#6e7OJ3|_S|W5z=hynU z(mhvSKFBq3{43ygrvtZK%MT|H(_}2pn;TrRVg(9hvV+iZrc64!B{%fRB%vX?+KMIL zcuC2*pjms0)Pg|361rKU&pSkEaos+%EJ3!zsK=z>L=q7V;98*>n7=4}GLZ-)n}}(> z@ERrZfVUD>vLrD<3K3CE){BGsh0`dS8c6t4EGYnurKf`D@{1=jpZ7Vgs)?3XaikHP z_T7*m82qRIvH#x8i%SG6pkbWPbYD7aAxlZV8dNo`%|s9AehgFfKk?r*o|9V=dAJ%7 z$gksrl=DXT1o=I|+U$xvCTTox{R{Tko{HMvtvu^kSc5tIc=(h3f4IIX&p&iL^zGo+ z%8noTJ$xUBWdY;(GV$dzDPB<4P1(QfhF|o80y?`DN`SOcfcS`?5wKkVX`VEFr((e( z83g~=YIemkBH4>f&M6L8jS1U9(?nz&bP%f`2_y(2`A4w$I4<_=)?PmKq)q+rg#C5L7kgtg-Fo5yL*`2MW_rn<1?wl6V>yBoEzg z&a-pQ^gr~(>QFqd8Vk0s(xxx=Fljn1i=|4ks31lOB>Y<<3(^?k1sD0R?6VJ8%ddTy zk`I-v0LJq0Uu-B)jM~RfGIQ;_wcxESziI`)#0&19UN8TC$9|*CDoREu)OJi~vnvNj zv?B*Gg7C_th`q>_Gfo$T)-C5Cfz0Q5`Q5E!cG)A}waE?URzzdx?tiPVuDZ%BC=|&T z1b?r&rc8W*B_=FDLaLLcvULt35SI(gox3g%Jn?@|LTI0o$YPa&gey(a{K?xZ^-sRO zJMeSjAzC0|9wvmaU=QV6a`;fm$m8Kp_W$APt~~$H@zA$}Un@I)1a~EVp(c8lFS5!NW#q|qm-+9wq8Wu`dz!rY5(|d+Qe^v zZGFwmE^?|nWUROBM-G}790G}ESfSK8*)=$A^dxreWJm)}Xzw~_!daf1=0D)MYVNz> zQQkvAe=(9v0uNZW4ikSzLhNuy9%}KsW(KMsT2?`;vBy1<`3sORx|1ElT6Jmv zmi|4~NRvQz5#o>~+WYIb*g4h;El|de8qI@h#~2`xFkF!t)j+s@l0tu*vKCU8u-Ya~ zSK$X<%7c26=sNeQ^hs-0WzHOyAh>X-!>xdi4w?sIK|nMQkmE1~JG7nROxn^FiKCDE zs=3cfgHHTRK4tKeDCDQ{y!;;4Zh)ewg#sIrrctx^h}U4U1c6tUKWI&lkv2ltcPWC@ zx7V%KKIeZRJV*oMj|hL7qH|C?kh3A}i=vZ_WPs9Y)w;|JN4uD4;)q6cY$DAAH4{7z z>IWpNO)i6B8bW3U$is)8;JfSQ=fz&#H?frDi+NFTdtO>bf?l`m682WIv@b<%-k*U) zBr>0O72lt?&5meGOJF&7&zv$FrEyQoxPELcrYWE~JY#)B@-o@THOrFvD=n9=Z!zbz z6v1Pdz#YSSGeB(wkb}AeX#l7Y2rV)+X~@8Co4ZMxgsjXS5VX9`>*87tnioVD z6U=>jN!k!kr^WqUty*i`)|NP4rjbC>9L6HR{3`6YHL6-XTyo47Px6)4 zJy*}~^|z#v_jFg%t>9u#9Id8{x)xFnjt1&S6h>VXO`&y5(S7OcON7oHHnTY2>8N#B zgaY-q=eFe($b-Qj%wKWo%-nL@iZqgj017%(lZ$7vG|Y7K0-5;h>CwU{t_ zZN}^iQ46769V3f5Xd-ZgMMHteLtvzN=&s3UxANR@)l))?^k<;7EYaHE+j20k$M<1v z80BU>zu-P?{YJxb9Mp6jH6M@bj4@a8f;z=Tq+LZaZlcbunFTOCYC z3u&ULKY`_-B51Tv(PK^5)N@aj2J~CQbMr4ooq(3hl4ej>Hjb)bha?HAThw>nUuu0O z=S1@R7(KFwSz0zWL>^Upt~+p0eNbDb*{hV588iPr)z^zco(Pg-4ry|5-e`Tp4pPTK zmcTXjoVn7#6ISw~;@%+36x6Q1i6ac#Any;{ZG5|p!nf0PO>il+&QP?D`}xDM`f9 zK6#Hf)M(#8f)IqH#I1psmcD1*m%VgYaL=656NiZxSlW@(797+q7&#U0Gm70ajRc!plR6{ECaE zju#c>gMDrC`!HEr%3rMe{A1qS4jfbkit=mV2{WF z+XUi>q-X)PBT6)7Y9$fdeV$QjUYaoE@&v)A7j5s_KRr^<7VI9`47C6$jEsA;5&aj0CocWgs)Sm)pIgUHwxgY#@G z^P>Pv34*cfx&v=T{@y_oLbY3sgQI{X4hn2}#IU~dh@oFv!n<*`J6p40tTecEMOWjy z?R%~7f?&GNJ@a_6PmfJVCdrLTaDN;o7^cADeZOlTZ5X1y@%A1h5X-(3Suhd~fkb|z zj3aU?g8VqBQ8BD1keHw+2)-pZ9ayvWfEoB=b8z>zuT z2#qP^nhvb}G_Yg!>phEBJkeB9WRV7jJ)PqZK5l%wZI4B06r9m8K%3U!6A@83KC$Z$(0GCWg#csHg%<9I)eDROb(`NfY} zdwtjh$AlK?aXb*b0Hs1~+djg;N8^Ej%JU0KfS$+J{2bIB^m48Vam;1xH%eD;4D9OP za@Tz~T$;D?tFP5;jW(1b(m*Z4RJoyP+%Qx-aZHrwypJXhhd}-~s19j|z)VbW(D;HG z2YZ|`pZ5h6sJn*L2~KmYs|dzy+Wn%WL6-EA6e$iW{%WrDJKfu^8H z3+G(4mbH?tK#@&~U1cRnB2R8ANqvTn3n-K;1{bwEl3|ovzinL5cC`XN&l>&PZJ%<# zU$j;x2?7_pbi9P6BpnC39@~B&k+5jnk!5uOYSFZ~fXE|GP?pckT{ii#l1<-i)%MGn zXH88-{>DM`M)MuIAqQYTk!UqL(6moN0zi7I7t&H%!wvJ>LwWfrcsvCRJf56X0MEbj z7JRkkX9xuM!Cq^{2?ZduwRd}4Si?Q%g5^THHs^vG3`f{)NN~^%#!?aw=$~#rBe?vH zjYxDd1XW$^)}@X6l9_MuqT+7Wp6ZR|mr|D?vUa%y^Y`7~1RnoyUwj7=lBe}Dj|FEH zR^&IzV4MuzNEEV9a6LL>|I+!dm;V56CP^AMaMV$A?vR&uN=(6onP#egzk(sT?>KkvJsekjGNYBY!E?pTv`ZB41 z-zRvvU$+EpQ-DJtkvWuE$*0P5B9C)y$CxK1F!#!-xl1OmDE~o|nJ6H6R7r)1%VLtH^Tofsj4MA(2KvWIJlQ2>0~xN}ZDj0i4wTEEqX#1}Mr1B#sR5`z`Ze zDD=qTbwdKnG$reJR^}G?;+q9<_XGcff+Fh4oZPF#9$f|3)N?ljH~WTu)o}G2HY)Gl zds^V%Prqkv7^Ulmkd_kf{p$_u%>8+TFt72v;_Dg?fkfs|W+k60&xts0a+Lpx%=6XR z`xlK~>;EaA>cgN$eH0LB&^{mBj(c?k79^aU(n(JB`ng8aVn^Q69{>^tc$qA0*YXZL zSiGSetQv7xq@hu#FEwgmH!W+L3EH*>xbCVEkdk;iWTX#*+&qs}Mi4YiVFP+q^+Fv* zLk+_Pty=y9S6(+5e*QI=#&4mpAl!TNwbz2j^)I|$LrxXhThR*Qy1;*)J{`$|%$)QL z+#Dmo-AChgd;W44hCR59@Qo2B6N@B ztVki>6cDbPmb-M}o8>& zuYU&Aw73PX}4*6H@Gtlqs=|@bml<`s)ac8Uh8diNaRep%uP!dd>@r_Ua`f z`F0$QJdg}5^60Cy@NUYw?55@E(>7?_xSeyJNj@S-8XS{JNdRWbOf)4X;NQAUbWS7> z>Sd_=bEb3iU$1A(d~lqn6Ojkl0|E#2jhYJ1Sb_jLjzU}thdXcjD|9;kW!RU~4!eM1 z*)hoGS9_sqYc+1=1eiUIB8<|A5YNa2URbPZA8lO0;=G!*q_(~V8(xFO`2erE7`2{q z%kRT5xYW0&BqS$ZMd8~?Gm89K6GGEi6wvUX)k{LM7+3O6;OD<>RddQvvtULQ-uxkb zM@$l3=2K~Bby}1@X+@&o{>MI}Bn@m0_@E)3mH|_;vg$c8`!9LVnnh;d>Qx!%KXi__ z8);Uwoo;RWw}WcJs{AGg@_mpc6Rx?!3k&c04|F*04k#>Az~!n-y9624MJOu1(()Ct z`WWNf?`QhA+dcwG{yJoLsZZe@_WSr0ulXh~D`z3U4^!;YaXd>&>WSJ3b)u%+k(~HS zwE$@uNrIbwr|w&`uz2G})I`J}g{`UrA>R;Vb;hi&bK(cR(NannQ^Et9n>FHg1rH2Gh?Q0u<4K2_(QJgygF6X9pt8v0x5$k{>Vz{;Z~xBAB5u19w1MYIgI zO3O2n3$4g9m!wZ=J=oQDP!oBSc9qkgC^Y?@aa2$Cji{SC#0Rj# z$5SYl5;Hh|Mf!|8&hd7$A}=^3Qa@-q5`#!f001BWNklt$exbvplp=Z~b z;13w}L>@MXu!>7eIC;=n(78i@6xur72;GbJ+2$Y3?EULdD9#g z5Sg`lN*w5FZLZFoH8)F)n-JtW?P*?V`Dg)6XX!v5ko@|;CXKw3v@0acrah2qBs47o zal1^o=PwsRrw%tjL7{Fr)77s46+lr;*t0hgW=*{mQd4MYuu0<`bLVb>=N@>5eiunb)&>O0=LMqtbA|aY+H+o zHOtJvOG`e6(z2}~O`t9XoTXWqd-vUkT27HAJjw5)MFtmIW?cgIv{geqdt-qH&sbG-wtt_s&tN5Olcsz$Y{$Ko7W`kyW6#LZ4|MP_nVc(PWh9S7 zN`_j9>A5Fm^uKOQ9G2;2iZ0OtF$Z;q*sn}`^v$4sTMx8p`7jFXcpUYU&7q+LC<74* zqPRqaYp=Y~(geuIgn~opcw=$C`$I%_L?^8!OtSL`DapU7%U)k(6#Rdo;A5ty@?=OW3eG9@hq1 zwwP;a0TiaaF+ia;+%5@*5B-ZZjU`bC6)xysZQX^({%aZd|F~zm9njJ1-DPb5c`Yw1 zC!ZGz;b92Jw;Rk-lTQ!ZLYgEvSLyj#V#3DY>#G-nKWN#TDv^HSA@l>xHG?KKf z1CSPK7z~mUIn256Jjl-J1};Un+D2o5jQFKx9NM)RZTW?$x{miF^O90SUHR4H$>MyBDawVYeI$mK?lz^)LG?vihYE(Nu!Q*U z)iv*2tiAtnAy-w~_T`}3VAlU!PZB(VV>A+rSn|#CULo>0M)9bIrTv!}<*f(1+T7n( zO5|i@kL3r77Q`-r(7lte96mG)2NUx`hU&(A9@{TO*OLzVg`j4D`J%CR-)3{y?scFj z6f3K?0uRq^h%6~_zrOXm$Bgf`F;HXyG))taYu8medC+)H1ST~2^j&>{-Y40TXrLf6 zk6U4>DPO5?ymg-T?nm1~Q-rmx%jSyk;3XFsIY0E}XagD?p$M%bGh-1nRZ+@Sv%4$n z%xe>d49C%kp)fmygoD~6c7+Ya?4dEl?5SfQ5a<}1_ix3*xp(Y~G`B_37lZFgDpBEOl#=KbGuGS_D%a&wS zj}=>txn020Q{TmBzB>5a;_=!CpKPH=jr+9?ZtuJW9CZE8y`LSJKjz|Ld}RcDmNkMwlBageLEheDl*)Rr978nG&D2pnWd zfWo57Lq^nSXb4C8g#^_A-XAS1UWVGj&QQ53JMB@y1cT=tc+C85SNOegmYCRH>fU7@ zs8-O#SbLvT540_?VTF7lZ+aw2=62IUh;85x-r@iEQ$76ueB|8VYi~ZRf4wyuX#aZp z9cdu;R>DyaA`X=sj5a`uX9|RpnZR78hsj!i>p@ENneymZg>v|xDA4b9IA}-vgYmyu zhM#L`q18y0_8Sqi-DF<}a7P}CRZu*ya;_S^Tx~}5b7c(|7^4*!G0}#i6|p98Y|u!b zjODlAH~w3^M(YMtmsvK z@56VRd$NBp_v|gEX{>|3J)Fyy24YVUMlGm88dk`3L1-f-6l~M--`%9{ec*F--zA@^ zdrsS;GP98+0eM2(nU_|MA{7*lOsEh9s7!lucS}ON)4o&5k(+@PXnICF!{on;Y@kN zunc*`(C@L|rdBL`Owe4vB>9bRz8e}`zVd6ZiNX|nbV+ngIcEzVZxbpRRh1BdLsKxM zX&}e4IA0Jvp3lwScW*L&+_47p-c?tvJ^WWxk0j%)39b-fc-+8_Z=V3cU|+uDw@)k$ zrUq!J4W%eFIz_zQVHAAuS#a;Iw-kM_q0p>(=&oK$CR`mqXkd{U7&t%vq@f}c`WEqE zmr^rG8!CktO$ZfV|B(&hU=EEugiJC1s~ZlyJ?8%6Px6uA$Z^VI37XO&ZKs! z^XXKt}A!_UGa;8K3^3J%fso-Gc-g{lMB=4-W3$$LwOXPW@0_%=Go91061i>pSc; zPv>)TjiR&YJO<6`fwhpt`faGTE8tKLofx$W)BR$aP{R~VZ!JS zV1HgF*gFeG*RzoDr0J^Ndmo4SH(!Z&M@w!Ev1`YE?(?T?;z2d6IUnu5X{riXQlej7 z^S?9n^&39mrYVD_8}hKhY4YhKHh^XvkFqaXrmslQ(bN)Y7y`IlD5~)zEhbRaFF;n- zm^nG?)a7sdh{S)u+`TvaI~T|@Ql&!E4hh4MFyh(hQzk-C8^V6+DDKkN|mb5q4^Hbeh z_81^?6GaLsPY#X;%wK>135jB@{`JoTfF#7sim-c?f9HRGk_Nl{23*7*Wl-_a-M*)D!lBL}`Z zSv4lv3LOy%4H$IV6MUSz6WozJ;WgVx|pafWI>Ov)1YMwYME}XX@32??Tb+;>Y!zQ#cZA zT$b@y<%Y1*zYY5Bef)p-6n!{yMftY5s6qUM1Yp;3)$NNak0ump|8l@2U=r!G$}K{z zl02FM=(&r`0Cbl#$K9DUGJzHp$c}GejS*V}LZeRVJ6$^goII#Kl$26lV+qh>!P;6D zN=g_E8F&Wt>YjjkDlaS+^bMQdW{C-36G1>#1W4W2_WM|`fBDVYklzPq32~-0WZ+r6 zsCZy#x50*3hDbsv##2A96Rkq3ej z5*$5axSi4O4M4!X;EGfdoMqzU9l4o8j{boWdIrTNXD6Q6_Yaflk ztj(4xrqKP?`8`J&Lb@bBKG!T86>8YAB@d$ju~lKV1DOWx+DOo)V+!~KVZjouplKQd z%E||Tp`QXY^`gj%@ypIa{qrwhV~O!7CatN6%0`K_iVJw&fqTv0c7<2k7%VX{S?b>9 zavYW0HrM*==yXrEO@c_9rsHIpLa39-DF$6%41RUG|Iw%V`v3XJtl)~(&*C$EVf^ru zh~+$ZZ;JI>!=!y#tZ6o3czH z!k&N6{7%cgHAV1Xr5beNqmf8+1l6~mXO=0K#t)b*&BCG~A+PgFh0-5DSks{WcWfUG&LtM-^RuMHpLzEFRp$P@soMMN z+n4|S!I|X`Jo1XUH~S}Z&)y(#(+#rZ0U{ku^Ml)^vb59$*1l~o@uF#$iZds#MzZ*s zrKTZb?aH|P&A}@#4>Rl1h?x_9;~$fYpURpXeU2pd(RSz>$^)g6mHnb@d^&B|gd9R+XLm!jGotdT%M7)6PE4ipuq;b$6= zi`x2)+s$8ht^=3r(6mq(^j#DwlJDIRRP8A`Bowh{R}LQcpF^eU^4;0m23^=JYjB=pG~8$fdOv(6ooS(lDu zP|LVhm~rl_!kFP(@YvHxF5~drZp)rQ26wXE5NQb3E^$qQG%A9of=W!4caW3MeIfO% zCoc5$CL@@Nf?P+TrQ=@}Nkg6L?QYKS^>oej^;GaP5{W@&ta6wPA{N??wK9jl%0MrY zh8tAg&viB?=f!oKgL}?O6MZ|7#AttKM-w>&B2^kNb=YT=bPM&uy6jg?9@S}+(F3vr z7={TWhjoOTu3infd7Z84k!XME@QJF@4`m-l6v>0d%HNTe7p zO~z$WNKM{c_Sb)o;DyDdmIt=&_c5Ba$aow4fj=^>PW>cIT6VpKHfdv$%)#gR-1uSp zT7A=(8$ebz^Mb+y#*aUTe@lCfP=`A-l2sHCP9ESxG8v2K8O(Zh8-jlaAn{44iO_S| z#_zheU(oaGdeC@jd14f=FNP=zmwThgI!Sh%$OI~KSg3< zg7rAu4E(2bbGE^G5x$^rJxu=fXZ^ru3yVMX+jnkXf{f?fS}}BVg!eR0W*+0s%=5eS zm$O#pn&n+ktD%@EhxeHpwx=UYADI&iz{ae)n4}=dUOy{u`2+j(Qd-h!IIFR$huC`S zhX|xoh(J2U1c6XHC8;DU|AOh4+yH@~)jE+pz;o5yb@)uDL3r?JZx|)2Yu}xvZ`ibm zn}&p=eJ+m}YG8T~baMpCx|%*So!effN&ATqc_^~&Yhjo{@cXwikNc&-qUHbNMa9{? zuqfZ!Ys7}jS|&{8==YX?1}YAHdU&DZal@ElOo6P-Q}Dd4@VLi{3P4vAmmd z{FQJ2q~)u$5(^D~f?HpkIs1eYg`Z42p_JhslHTcup=e_w0Mt zYOA`nq}6XNS9QBLp&(z&gF#x!MaE?$72gCF7k831{Zswk8n$O}(rSQ~+s63}mYlSU z7ngo$?#;f^*z#S6@_#&ZQrVsNKWu!z{X4uLA`dcz(-LW#o(iIb3$XN5&eBrbi__0J zOPn=%5j&yxUOdlQNJ+jJO3OMTaS(ZsEyJ+3__dAbO>SzcHb8+S;wab1H~nUgQ6^v# zF&UUN)CS0x!%Fm-@P7Hs58C53`nPDKl|ku>fXs|g0DP?O^VUh=ne#0jiQs(-fmc>ha)` z6?=dJGm?)6g%Kf-dD{>kVC`CKtXrp!!myKO2{X=nTsZHHPeD;uq9!;4i7e6j4efwP z_hi{KtS}@FX;cnT!U{sIfrk=l5Jb>a-v<-rg^m$9aNAEq<)vI@IvA~b$mtJ%*>c|c zM^eVko8s%?)i{m{PzD~-c#Z@)&C!;$WJn*A^TN6>f_tyakm7=5V_|lCY0e-PMYUY) zm}8du!g+_4a8M;;tD;0vMq#JHh@n&A=4%#1ZXN|0JentW^neJ&>*26x-z>P|x<%pX zIP%&SdvrYsG@V93wC6|A{B74-Gdm{_WQoF-Xi~r4gWQ+ST4(OdiNicPTbTI6ou6zP zhuJe|s)@N>KbX1sZ)^!P(zobq>#Xjr4=RI^4cyc?q>;k3QJCzWhlNjefg>W)s|;5 zJ)?iR`9+I7NJG(dT}Vw)u0i7A$pY^~U|35J8))lO{F>QYd3 z+rf?&5ztawU6+{0!&q|CKK%TJxogku=C8YYlt1uj|FWC!x=nlc!_DU2?0r1%08LHN z>EZF7&Me3cYd??3--Hq5x=D@U?xGL#o-5sSlZ#1* z8`GO9NP9zKZS62e%@8{TVmER!&dNv}?L(S+EH!*?Eo;q4Js!GtvS%8?(@lm+!3fEj zm<-;UwIP-Ltc;hG?lZRi^cYKw7xDLG&k$K+!mlhnZI%Abx7X+!KOYrbvATcYv1jiJ zJoVgyCp4yv=xeyYxfV^qu*q0TelU)ja-SJZ^!#-VfkoOv}xF+ zaa1F%gT@2t;y8NdrKSocW^n$BjHwI9dO89&BATD9vTsmhpglh>%6ok!63BAffKqqy zx?&YbtQ{b>WJ;qWmI#DELd}iBu4U+Z?7VCe7;t z1OK=9YGY6KeMlZJ1s5%wV#SAPC#@6pigRyNv9uJ4V-E<+`e*HX1 zPqX7!RSfrHYd64-YnLF5AN4$_8m;WI{W_>v491*l<{-ZiYpHON8N6y`#*Bw1db?7T zODjxDWZ^Jq-cx2G`)lEQfA=w^oDA*Ie5Y>0^+ao^tE@8zMOM$F4DjFW|{ugmhbh?z92h>46WMKg*^ zeJJpVK#nbdqY)&mMS*-Qs)sO%`2Aq+%_d_uO<{|m$S58}nikWMFgRR&`8XImYBOpb zS3p^r1TI%;S{L%wGeWIuT%{o z)l_DJkYkn_*A%|>R0}D!D8TjRZcukLkQySk2!uw^rw++Lp=qxoimjI&zSl%dT_W<1 zj_@=TC5moKngzw7(sdmso!t_ie&|scHh2x>ALxj66SRmW(sp6nLO4rK5{1G2XF*wc zx~ax-tO#a-7I5obcb<28c4X;e5$~>xqJ8%$**ddT`qb@A!77~7y3CUOhMW7`?ADMr2kinfg zH|_sEcz_pfx#nIt?bO$xq?C4M)Q&R@pXjo~7 zt`xE?T1#+SW*YF{M^1%nulN80!5iQ}K>`vAX*1Q)By7@3Nos#kn{m)&LBj=^iM}DO zHaE5sp62B1AZvX29|7Jn+C>H$$B7e_-+fR)9p(Mj~w;Rl_C>O+Oy& zx;w^&9mFyyh8FGl-9yftdPmZT7G&Ev%H~woW>)Efcc};z?Zjyt4yuRPAds+-oifn_ z9{2H92#?rnJ@0$`YFiSpKislB5!m4hXsE)48DUV4s0j_?USA^#rMk+EP<}eV4BuOOJEa8Y&CXLr~(1n74RW= z{DC*&mTRtok;5*5IrZ^j001BWNklplVaC*i>Zes2hhr zdf(}hnGTvP$Z$?RgKhU2rCmdvq$-;k7BqQ==-Uv|NMdz>=AZ!)8w6rI(gFN;(Movw zh3Sx)`2fWEwt#He56BQr!2#`ZvC{BuTZaMrat1@^jt|03S3d*ST=5K)m){44MPnhT zl96m(V|S=QlZGlA6-r&>i4JMVmex3%J5m zkh#|>+y!#cKqeTQr`+SSWU0)p2kApR3@H+F(X}R?w&z~;iy}yF{{2Z|@R>TWM1YnT zrT4=9Q#aMDH+U%V-=J_CWQ4TMVI3v17R4Ey55LP#%1XxWeNNv8q~j`w7mCCgJo;SD zp?|q~;ti~nalXNRg>KRXAhTVs*q`3}Ja@C95sJ+;%N!2n`~*t?y_b_P(W#7>Rpb^<4K8bFAq26vm`W=T5R* zSB0j7T>l;BQ4pb^PS%}d1G&Exn%FN#FYX1ClKXEy1I6^Id`{NU=u~o8lFmQ&;4+hp zl_UJaHc3SBWYjALDR5d$N~fTs|LD>8IA5i17;=BWwP4kR5bTRLU7a%%11<+EkjS%7 zNspmCRM1sl`ju5#tXaFnnJln6>uOrw6&$e7HOUHov^WJvJZO@GaK4~w8fr(@du zD*7a#+4xmt@_16T#Yu|dKYKReC)K>c66rbsSgkoB8(w;0EXq6MrG=?UI&73}FK|h= zFy957yPd#cflKz)DRrKrHR2h&QKz3HlbHkM$r+%e`94VQJ|2Gop(n+PtkNw$YCK~4 zex4=HdIT15(+1RLx#xS)KE>1Mp4mpKmgq139GBrmXbMNFCMs}O>?b%CNCx_)BsOSr zU$&n_#PUF&Y*WVKF-l);#RryHAt=Mn!EQK*=P1+(_R)KNQmfm4z%0qi`hNn$3uUVI z5_81Y=Wt3m&50HqdUKhh=!+HPWuDG^^*%QvnQt+WBCP*T_+EYr(6`bF+Q#CGIHE>q z6NbQTkToYZadO_^i8VnYOmg>3Oil;q#kignZF=FcFyeUz8DMT+T8}}4Td5C4t6j7C z-ei8Yb&tQha)c?>v8_62Z0I(1Z=c*Z_yeyS3(wt|+nvWPa8^scUsQ0_qQcm*sl#P1 zNB}OlSUVU`ab&8zCno`r40D<|QiFkpUI)yV`ct3s+ZI-XJpcS(I0FS0qs+Ps^fmk~ zf;fx32ehTQ`iFC-HlEKPi0jQ9zYWlMAs0~GZevX_sf>^j=*WY6ZR8^*Fg_Ys&U>jB z6(@WFqVqsaSYQ#thWze98T)qQ*JC!!9qkv~sopnMcY|>N`_7vYr=vYR03GzIhM4(v z0c5~<{c0W3U_2F2Vi3wWoOd-vJr*3q z(2%}S)yvJ!cC+bpPq=+NoBirrObqa3{1s?u;pm1zdvDS7-zsn!&I>m62TSPUx_{8b zrM3abC7ksNnr*BX&)N6oUz;n8!5Lvo7!-C3(K}{hFWufS+`u6rga3)tN{TNqIy82i zmRc7cIVzvJBxfqOm0J=?kJ=)RZuJ}%AIUPK8^}4avt#|Pb)cf4q*dg)&rMx-TA-KM z6Y`BaEw2KHSw^&gJrH{Y`v%&q>o&);-)2GLEA&^oiyL4$_l^omsjLD9jZYt7_5A>^ z{?Y4ScQCCbfWjN{#2nErg6KG?WKa)d7H;?$5~dki?qu}3zEfjX3aP8uNS&O(94%z9 z^KxE1kd}qMf|lO@<`7_LxrMdQG7bo_49x3d8He31w69wrz$?m9kRNdMZ*uMal^#)C zhgp{Mlu#?&0+SU+S8*Y@UB|caQ>rBQCBktho#|*_$O4nW0@JmNwjrMTX$a8$BDhzp zhh(;!8~V?ml>nN>H@I~PYr*nJPaE-}jo&&*i(!ds3fMH=Om-j+I}xOHb+5^%$I%D^ z7a4=8wSr@y?dlYto@x1wwcYOoEdCFxw zD^<;aI)T=tC|UA{>mP|h|7=Ppgur;nW3m1r$u9d|#Csl2F-5vii#U|T7KN$BLVNxjw^~23MZzK(Y{0=t;MDD*jW$#?c0y}iq zzbWD12(chYqC&znL}sapCFY(|W+Eeu8K!mw|87+OD&uUPcYMBn+E29^1kIQKAaac2 zzej`nyj7#%`8+_UF}@(J^%O#XzCOa{o$K+7{)HTRGoRFGNRD$NT30QImmbLOgFL?R zBbcKz9n`+I(@EehhOh2O=-0f_rMzRGhQ{V<0zN3q`tf|wVmX}xSaqc!kOZtzIwCID(9^!P^N#fapj5b~dyEo^&np78Yrp8B|mugU_pQ>?88UiwUs| z)m0Dm5dDgRF}T@$iz8?Cv*pQ*|E$a5v??(Tp3fq!%X zUU(hBJ|FWTU!hmM8+X z*|fqO0#{9S?co!`BorXa002PT;j#YAPzQj8_X^3Z69F3Siu?$ zR=9Ng=k*ly_mYTgp^7R|Fee9=GQls!)P1y9sIBnsONIQ(!+D^g!LGD3?a3e8m?x(l zBPPe3O9xOQoVB9h@gl8B5@{W)42Bp8*fUPa?YbwVZ@x(UqcrNy74y`{F+gMZ_Z(O= z`wWK>$$XektRL2-gu?2MMM`;?bGF>y(sD=qfq@(c;7>4bzsDn!imkNke#y8W{HHgS zrR_<{U}Q@YG5CV-K&*k$0r2%f#o(z4PC;=Ib+ia}RbJ#?XM69V_?zF%ic!EB7S9syKu`xB zi=I|)=*6fv&=M#((>zb#oyB6S@#`oma#G;b_0<;3YJq*@+bVYP9R2?a>ZV)&e?cA9 zPBt9b3nZPYVFgpjjpzF@?qC0?=luBVLvwy4!stM(vgMOQP!V??BU5cJj)WiDp+c*7bMCtvC-rUXjfH~xM)jBp1EA& z@2n7KRW_j0t{b&E{*nM8YVbYJNu`yS2r`Ij5hWyhDdYK~1#sfa=GWy6g5-c5u94$0 zKryWFw!E-0S4*t2hJ*}*uzIg7PgbB1r!0>n>+%{7xl3mQJ;Of-sct8287h%lnas+XUCWw#x+vkN?e^;o`LS#N_#GNBl zNH}_(!I>mZL!4li$bT*%E(2m}1VH9-g9iPSSjMRE{u;h=dfOAd+^*+qvtO|#wVS6^ zss$lK78aL7o$P_bq9VL&ceAkI;boKDZ?hR9pnnJp;%frZ_$*I)rSro~oZ#BhzUE8> znH+)OmUOdB3TVUeO6V@o|NA0^pCQm8!}bAE(%`o@=c+ve{nM&4oo_)WaC*d}&hsV_ z!q>&*--J~*3}Bg2i_SIe^eKZr3g5;gMxTOWI_$-L;ChLf^4Wj-dh=ZnEB9$O|4y*Y ze}>Cm=4XyTwBMTWYZr6Rj}i!`dN)`%Sh zb07@WA`^IEzaFkC8D_w{#0YxsQqW_ZKI!1?kVIZ z4z2?7FC)qs0%&PmpZ1<%K}58;42m{)3?)pgLi~H2aY~{=c{$mBbB+g>(?1rSW99}q z1P^h-15Pq?4+d6aA)x*uvpy2T1V%~~5>8QJf#W2#iI#}e)86~o)yW=DW}&FU%hl0F zJm{#B`nn!Z>4$$95=R!Ht-p;X^NOU4=D9P#ZF*WzQvxY45ak)ENlobMO=&bmCDxAF z+)uCsar%@5YjB4mlfKZ9bTRN}4TU8Gy%-p_oqM#TWVyhoCdH}JI4Wj6ZR8;O43K|5 zmv99ruLW%pK#5UvA5jUkyOtlP+-mPGo0uN8Eo(KXS@#z9FAc{1LQaLxf<(+q3UR^{ z?Gtgrc-pH60i|H=nx$mWwz$}JM}~?l*md1kzbuyU@xCGh#Y%B!Y4~KNZfPpbezwRqWfcQ(wwD1@{-Vu9<6lumT5=|V084x6@0@V?w{N`3V$N}Oc6;u!W zcQ7x(OspNJy7sBe?)*@ZkLPINVJh^A+%O>mi=J3lJJbW$zIwoRy=<7~J!#gW&CrE{ zM+)b72yg|!C{y$T@_A!0G&wmh8+nNhx!xxm0WKW3;6Bu6t+)3qg)hDA$HNP>s|BhD z@HSFfi>&t22;p<4N+#^G_Y590rL?u`jbu*$?wsdKH~Y~ofs@@KMWp=O`h7?RHjj;=cbH9!TGuJew7eq$S?^pE@q5KzUp$B&Hfrb*{ zvA=N6VVd2p`J*8H^iQf(Ys+>uU6?S2%N$hGg=L-oaX9yLkTIp0Hf-|WP01g{r~#2rt*fAg}n zO$l=)u&Ew;6wlxOKEN7PtCSMC_LuLZMI~n2@x_ugL7w3U$sh_4B15M&XPccg7QCDh4D+|W8raEBv`ZKJQC)~&t0B7q>;ZBkt>J@ zVM6)NqvoZ28e{`ae+j*BaOX`g2&6IR$u&@}8oyw{qcWK|QFwrApYm-692O4qgIl2R zM+)C-c1v#%1ThBE$`k&ZZeD^_o-5IocH2$6G@&j2n>9Zz%rc8#m}XeKS(1mWV{^~` z?0w*>@AkvQIvXim;+AxSw&DFZ2-JrxFR9CX+n9OG(O8Wv#uRw_#uGJbATw2@^&~8i zEnja+<>^ZYwZ&4Gs$y690rI94BQ8QgIDdv9MG&z7buEYjTcp%MkScAdl#twCVcv6L zt_udt$x7ghiVV>a7eI-jpz_o%ph;EOkl#G$zlvOE=&;U9;rf-X4IP|U>yD;b#Q)K& z_TQnQJSj3WJmo$IJU1ms)DGPOOs0`)v-qySStq7H;kLGB@Dyn40M!aidU!DEvhC8` z?x;w3euot0>s{G-9ws??Z#Qg6NgR=wuZ`G>f;s#NS(Rjh{1P3}yDV*s#SS7_&WU=M z3$sC^mp0IRy?4AY0oJMR+-qdH_!7qcjcBaCh7!}mq8YWiFG1a~i^RC3H{Z9>C8Wdp zSmT%AC*_Yth&gEml+n;Y5_;p_Q$RQlaHaVEp|J8F%;g{Y>#U?kukD*n zyCky335E`zBUl!KvmJMTg_M6kzec24zec3B7t9V$#(-*$Ya;x|$_d7h1vzD9ei#bU z?xl`ovLZY@+`UTjvsDD*qIV$s0Kf!getyQuS*)Y!+2`uJp4&`yQ#S(v*rtQIdW}px zEIixG%eQ*;E29ppKRos}EE{k8QT7CJdmQ>9E!j&pv>;#J{g0>S!XO5`zwmwPO>MFf zU6*<`jY~_XQ-Dng4(Ph>3BmHm|JwSe4w2Dn$KVNl^!{k~M??Jk{?_2=mnubuXba35 z{@LP-<#ChzB5uoTxmmWZK#*2_*Rj?Vx^&H3bIV?7AD~e;p}=?+StbF(0{v;gGf82K zKE1A&mTZ5RdP}m^_S6`JP2P*f<-R6%Pi#n^kl+GyN{d<=v)_Ge(?2_HUX2M!{5QtI zO(&@k^`%7t?}G>jh;M3$`Rq0`C+s3vB0k!*3c#`#nW18gfdTo;qW<)lzJVy#;2ruB zjlmgT!@m%Y%OD~HTF}Se_om<_s*v2r^bK?k>XXqtDoH+hLYFtXe$!u0MH$d z1%{nK$>0DT5^8HQ@xAX`_~8ipQCy*Zd8VRt7_oG4FGTN|GumE;AS0$HeWUlq7sUPj znIay+q#+Rb`Hi%*GkWsN5!x&Tl0yf&UzJ_TO(~O;#deIGjdNk-1e=r!rlOCNvouEI z{p|Rf^6`!1S8H!Ih1hTHf+*&IbUV`Xj6h}lQ(}t8mG9vbI(QRYzToicj^9G>5=M~G z?@Flc>y(D?(mA!#rIT|L37D`n?}fI}*5aorM$+u$xt#Tun)5myE2Go6&%l_f zL|#$`ZUPwD;LuUBUlBt7g<4Om7!S-YFP?RWCN%*)6pV~j}H!X(&r9CE5+ zXb2JuZ!_Nyr5R#+Bea;_=fsb)0vw5im}6LD7i*lcas#e-)!j#9f)DrdWqpeMHeOqP|Egj@8}C`IDQA z?+*fa7*&AgpGZ`NEux(j6mLXmGcjzDI81ahn&$3RGZ4ZJq_6(UnB#WRtyovG%LH24 zI@sM85D%LnJY%z@%wj*MNM+ABFWqsUG5fl7!p7KJFwls60}|lF;?*;qVaLpCo7iLY zVt9CV&z4NDhLU4#f>k_q8khmd0=RJIJ}qA{?g*}nK-cwP7u$3w%!nHkdYRuujOIZS z-*6)09N-Bn^D~Wy+itaa1?m}Vv-7X>$kmp&-doM!#*V#z&>H1TIjyT<&feh4iX`5r zyCxrZla2(hR>iWXWwh_D01rtVguEU<>2MWpBI|9J2{8-`(7e8M zHK>x|TAr&N3fZ!M9x`30a#M8sIdw|~H*pTOf+h`U z&>&Khz1I0Flh_EN0FpQ1(67@e9Zw(bhs6_g>y1 zPoV3$6BhzR=Z8a&kOY<2LLVfE^|Guq1_GxOxwLe;4Gc6p@kuIQxuJe9m>en*u zBJ)`$_FtYlQk%^__~In}B#2UeK}x;3n9s5N>Lldz&lC3+Y9=#Te)CfN1SV6&fQ{kz zs#|kXw{Cj>mL0k&@Cpn6_t%I9QN(1J`;Kxa4A@EUwjV0Zv55qg1l7JIX)+KJaEE}A zEDUQdvStUwmz*lyt21PXj+ip@c>7syz3rIbIzI-?bE#sDF*};i;(k&g@EB8=&aAk% z(OmD``KuJ#sYd5Wl6o+6ms)XOAZOMen7iVa`Ao*_MZR*FawPVl8B83rI#?lTmBUTgB_li zGe7m3($Hsnz;em{hP)*j3LZDH+>8C~u+HWo5oP$GVd81xqoSVtCI}MqBuRpjRYB?<30s7&Aa%A!Q%3FD{q@a;c{9(roGJ;Ox9X<#d=5&P$zBVh7J9ABRX6%pO$?p zx9|90b~)!8Eyn$h8tjS^SKrqjYA_j}zZWX>(wde|oxBgmrm+AE`~uecq}Gc#hSia} zs6h^7d-Z{`o&mgkcCxzIE;s(tnjfq+sXV@VfS)L2Z0@g}dd8v8<(i92Mc>(n}7{|0eM zlHMD*ysf6b?YO^SdVk4nj^D#DD?zq3G0{hePVVp&Wtw?29CWb#G*?X0vU#N^?cC>Y zeSt6TWy~=bg9f>~Adjr~6^aI&Cb`|o-@dlkpzYl4XWstHWoCqe)etu~ML_#LeGIog z-O+n0t`tSg?d9`oi4aF3E(p6!BS_!#wpfy`z+dZiKtiNDt z9#jiUU&yz?Qt;+~P)!@Lb#=k=tL$xz5K6k_nZV6s0eQkPya5X7*VR9fUeYd}ByiZH z(aK|-2cxFhvBa5eliIE4n&vvkUu2F6}JcjwG=HJmq=r zj|;pv!9xNbTN~%WUo2^w(|8EiPg|B!iiAd5!WDO`I>9P5Kg>5~trxf_EXy|T%jSTF z!bVg75@P={%Y||Kl#5W4iUk*c(M+F@;*HqMn|L$YwjsXf?%1RDTyzDQQ~phay>R7C zSj@7%mCFiW?vI6OCcZTqj%>RDvul*t&@x{9_g}jDKDJM|Tq%He)Mcj0zbI9Yk^1an-&`OS#?&!;GL{ zBATv3!6*kZX~ldizj47yMJ zm5Q7Cs_XrT=W^UC72VxylvWd+Z0@?$~Lxlh?w@nu_B8+b^oMSPvPi(P+Ub00x ztJRQ<%h&Ioc@$JCbLye{>()wk-_feu=`r-N$U3B**1=!54(9%+A&Wqr#W_o9rix8j z4-E1c1TQsP{_`z3n;zS3ltqUACQ1M{Pdkkl5r(RdC^f3A0%%}Y3@89Ukxaw2CiVCj zpCQGoS@Oqt#&`~>#-BF*n#-PYavFd1PeZA|ZY{a(s=$H0d)T4?V=CBm2wAzhtVfm# zTY>~c-lK;$dUr8`lQ$bBsR;YyJ90=UrE{Q{u|mm>Mo0##qOWg&0#$MBNSV%k?2a_M?5;qW1m!t2FdA4*CRF+5 z)5u{3u7R~JrPG-ui9@ewdF4%{896|ju#DBB?!B_lPRAb|J{fgWs{r!p4qrAm`$2`Z zvd~vFi=zTzmNrB9g_LfkI*3gHqC?lE1s>+}aAKa+v;e7|8GhY@W#9 z&MJ5@@nMHPf=SPXHkaL&ZgkMy5B~0-kERuLNzUj2Gn(*j$xYS%ahppkd)~-^WqR8E z=UZ&E?ZZ9?H-2vXBtOa9ZK(Nl@y*{!2y6%TN8%9`1!0c)qQ$q+u`VIqzn8StX`o>= zC;hMapd88=@DmUd_V%7)MwsF49T!AXYYagdG6YgZe}g|i9A;>Ywf{Bp@gypC z`fTjz(6=BZMi#A>2Z^e0X5M{$5D95VSV2?1OlHyIjK#K}@VmgIbAe^b`qVodQiMws zQ@#%}#16q{@$QdGQ_o?g$mM*LNytqIkeR_<>_;N)a9S?Ig9>^wo>Ewd2hlJ3jD|>Y z(>vvB>h?VuMTZPR_T$IWCJo)_zT_vE36GT~bv-9EB(2Vd_Sg4ZVg+fe%MW|biK>l0 z#Jr`}^J-og8=EM_z@mw8Evca5Cg?b_k0Vsd>GeY%f}Eeu)V-FKwARXs57!KjNIyQ> z^68}mO2p>S##D3Wz}H~CzqP~B{3#rgD6piUxm{iF^(p$g7RC zsTJ)ACo4<)i?2_KODH)ISJ#p|-Ir(nt-V+}kD2iLipk58Q(u^ARyOiWul(IKYAssL zBpQWSeUpUw;Cs{tYMQy-X*;XGTQaUcd!XnK*oxhN{FWC2du8<072r49F8-14>7mES z{NWonDH1n7D*?Q(*XGLKU2=RIm(d1LdUBVmI-YW)&fb8Q`IW?Ylf0Gw{Q}kIZS-Y$ zno^*KcqZXeqk)K0GGdRrE*Zgxz4F1D&Cy+sIIC?(QkPCTlQ&TF!v+2_?2`?`6;p&L z!9^F`G18ckk*z7Wi?)82w{W##pYBANU9fj{hE|Ya^pvF0JN+lI{d#-85N_;{z6M8% zN$N3ua5%dKzKk!J&4zC1&y!^mdS4cY&IiaE#|X%r;-<6^kUVwb-Mjf;EM8wq?Af8; zrv$SJYZKZB4g?YRS##piO0DrS>EwKUU;V#x4`)jTps_Kep| z&1`~-TTw0OrUsCelz%sHtRX@WLxZ)SQ}gxoxcqQn0_;`W`v_}}E3jFcSDE41f4qHU z>lS_Pe0deCXSw(@A=6R7olhxgT)v^a#_ILRRsokX3vBwW81xZ;JlnYH!s7gRtYj=)Mr9V;OOUWEq!+#O@L z2n7ioe(+Q}9(4tU0LAOce%LInf@8@sGTS>g(nUJ?M6U+-dsS)6NJ^TLjeaa5 zNPuJqfg$vZY+q0RY=}k;qQ1+!LED%SAtVpOPG_}S6?%QPxbGY%riWBxOB7}JxdDMS z!7}%>&COy#S%&fhT{NuKrY5cxsRC}gdB}nJAXF&?=RPviwal?QK9PE?d>TA#iL_2S zp;fQ8-XZ~=zy%BeV1=&uE)2l|=XyUhPmb<(SDguuC}mWL1|keox}R?-!vdDf0Gg+=ke!0VTffLS^7h@%C;7|!c(YhCNhj$EbxW4 zc8mEa$J-rfGSVPk=0#Ab;X$6_Hz;HvBA|g5W!Ix;SM_OIRsVOIfe5NH@CB{c<>BKQ zUpX)77>m@{ukp(}Ra{21YziRx!&om1Oqyf}j+PLLjr+3EBr_Q~mc)^+pf8R*nnV$o zVU+a~9S~m7^4uF1G8qv?CDv~bk<-l*dSj@ZD5O+>eX^QU8fVyroq|XDa{^@7%1)Qa zm-80W=CMWQ{ltc7m>X0n)41cvG2W^~Rs#QXVfVCH$L)Fbj{mEfowyQ{9 z%acDByt$wnCzi(33yE_|WQjYi<)f`0x6PC?7tmuU{iOWyl6XlRMpa!reU@*Y z_r2uJOA~y_I8DQ->JNLeXMq7KeM;hJBd%u;t7=Ul`oRk7#<@4;R=<(SxJ^5bYpGH*HKG z@Rd|Rnc)^*%11kP>YcyE#?LLZaOxS^0#@Q-GD2n|fxv2)N^xQ)4y1YEV>od-zP>v* z7fdN5(^ogQmbj}}gl5oH8_0~P5|6R3TDO^lZ|Bgyl#6+Wk^^H*R47!T(boD0Mc&04 zOY3Mi!I49~>C4jGWBP-JLhkkou*{f`iN)w;+MfR|2~rUufUIQr&pvw%%GFN2p3%+; zkC5Bn6d;uxIIZjz1X)6$?!>;w$nWb6=4DHlTGf>x&Ey3rs6aYUH(z(&s;GIph2J)} z?ku}q&4?HTLE7`{An{@B{0go{9MjLFMV{vib*q zCbAs)keH{+KYZS1g@8RB!9@NID3#`at zVbmafjLNI;f@2QF*2T;s7yPcq9}5f&p4lp6pw)^g78SLXxJyH@*K!o_T|Q12!&4P& z&8$oUEcvlA2^@zSW)dtA%`vTd(;YcH>NRVvC~(NsGeuof+`D)6WGysCTqD8$5qy<% zBa(A2D6BmM6#%-!A#WI!F};`7>feQ@6B-;X!)N5yl4(gxW?+%QTT%tIyB$>*{4;Lz z%U*t7Y&4;EVv+I3*yZ289I4MnbXwD3k>0)&Gd%izVTC`+sAi)j7qso#1AD2t*XZjc zZ})2{=>P7FO}JoSFcLsm^h+$y_#06Ng<};x@QBQ9D*REMVTDeS4vnteVseD8TKqwT ztbm7KDgetA9Qoxvm+V%aReQB^Wvap|A6ti=)b*grAnhUV*zo9HC?@HfgfBVr=#Koe zzT4mMkRj*sD1_}>ELu-!+<)H!(S>WAF)2}719Zn(;@&G>QH^S>mG1a*MMu>;f{1=( zLS0pFrTWex?$7mcJ-#hJ)u)0PCfUhmY&IAAf%KSk1zoL#w^7ek*mT47&CMU503rJZ z&chP^!Fz3?_z~TgOJwq^X4y^8d*j>G%Ub@-(o@^JESV0I5T@C=8bI(B)reOtnNDXJx#oSzv&jI z=t#$R?13<6^LKOKoqhAKE(8hity5;M13V^j{~%s9oAy4W-nAi0 zDY;~&imSY;rcX(y=g zju3lH!M6(oQS1y@tzZ-ReY0**QyQn{2KFd_sMVslD7KCDIpn33huiVwgIW{*j7d!` z$yp}ef)ZbhK`y^PkTESg;DAu1t(4C9M1%zX37ya*t+~4GXozGq@MVge^_+@ma_x3m z3J)Sq3nUTR`&&Wq???X9@en|`Q->Y{S=|&=D0ZlCpwUQG@yZ_3F;Mw;5Gv-wWiI*7 z=bgTo6$$+BZ>0u$7mnkuTim1n8fZPeE4z9tG<9!X`iwYE zv}Sh5MqE?FL48u6acIbq6B2UQZshnfEUBRtX%mkPy|IeV+PRM}dW!jij>VB0Y#a)s zf}tE($=9>HtK&XVcH}Ch^}I@l3}yXv)-({N;~2NWciV)qY}6T*f9%sFV_A*ml!kMs zV>Iv_clx=qJv#A_0b(x}$pUMGnJbb`;K+>L!C}tlq7LM&@&_@fu?@@+K{3l^_~ZHA zG)(GQ2Juvo)1QS3@{L7CIw0j8)yZ^o;nmq`_2U!cD&!Sn2awSc&>I52IHB+$nJu&T zLLur4`m!InNS~Jj_&yuQZwCx(d%Bh-a?Up{HXXG3aFM)hw)8H;Y66)9F5dZ__>Gpn zmVbTJwo)zn3JslU;lsC23YlF5%aUij;{Q+W;%bdCls|gE8?dcYz!=i#{T~r5Px1nKu@y+LD@|rl}lKQGLOvw=) zSx<5d$&hhppk|ZPTcl(QOQokwdKE(vX-e8weC?P0rsTj_(Uht{)sOn;So2>pneQk5 z3Dv&y)6v;hzkLQlaa-c7RlUp5x}X$$;Yq1+e+`sAiB51?usIln7E7^RQjXqD#8{y?Kc+|c3w8?y)gZSmC0*4H<0CGj_TSjYV zGd?!{?AlGi!NVg66`KxDo(A3XaB1_rI?1WZdik+mY)2JWAVO%IkhFeO9?S9Yz4y6YKJY*8 z)d^Gd2j?dsdwg`a_KK368#J1#{ATG4Fr+XNh2K%z?3bOdyLVn$fo;ZybM{D!Wq*ci zdzr^e=nXD-(VgGL<7gfMTOC$ix$)k4<_-K~G!vcc;fvBPW}C@Mm*7*SzHiW{VQ?8e z2+|sG_u$m93x8fWACcM3p_G&lHHj+F!TPlZAV@7-HQ*`s!H2mq{DNqdRC4+X+>UGe z^foDNMyzz;;@K6;r(iuHalC%HhqZ2LILJ8WduCN??A9**Vc`z|qElKf@^4Fv*jRZ5 zheFKYWGt%8l+wYCPV3d^yI-i^kP=E&0QOX2InQ4Ey7dsdXK=TlWoMiNqU*0H0U7_7 zYS;9NI9@Wqpaz#E9^L4TNvvE4@;ER{EP#hR9bpcjN0($Ax3LH;^aDF+wE2dimwyHb z8P`aWE`N>3SMmpGsgY@aJn?y2BhZmQ{6YFm=Bc~x%Q5=ac%ffF7 zoA={|A}#zg3bMRRi19#Vg=H>y{m}ybA*9J-wk#WP)~e#F!Y{ockqLh9q1}Mq1E}5G z1JB`1*{KI>Esj4{J5K2MoWl510LFgDN>jhVQbA~-N=*&gL9K_RZWd&Xy%iYWjsy+XZm|{X z+oWzw&MdLdcyK3dxbrREbzMXtZ@?G;%Up!&Sh8k7V_(6PY=9}5pbo`Qqw)Pel^H)V}$EuYfMPI1!CGjfZy}_Iuv^-n`R_zrX_WAOlE~8pEX%Ef6dq z5i~+mbduV$D1hv_<0KW*&1V>gLl;R4R5x_8d4<*fUu-}Yg>>h|pYn4#CnVd~Rh^qi zu0a~1e$+<>Lx^d`KeO;@B|uJkCT|zZuUKP(eaU>k^?+;CnLYblmc)!5 zO#yzj$exSK^rjw12o-9zd|2JgGT0PN%mdDLfbuc)hC;0YOXJBSa$ z2W()OjPJOGSh-*x{79Avh_uS9mifhB8oM{UR^wS;Ywdqs2A~~X3`MGH>Of!kaO@wY z(5f|1!s*$wYt`sw)6i_-m1LDx@@a7u-hLPG{16x&GvTtH4idWk(|Wy68*Xhro&Nci zDYJuhv7I*D_3I^n)|a@c9JyD~!wbL>VMH#OR)w$1;3Z zZ$xCyVz~b2x_+3P!?50fQ-Ah9<9pa)VNT8N!X~AaVk_DyeO+B&Cm-j%dv&t8UH_es zz3A)+M4?|P_Z6#8W3t!+1(!n}yED7=i_Wj;;r8tcDxtqi|8b6e^|>GUeMoK^Bikq` zY9DeM+3(xguqH&s0ypPy`x_gr8fMU`icxv?tPDio`j3vvctmN=?v1SE zmTg%@`Z(rBjR0`suyV8{oM*kJqT}NMIG#?9+a3WORglz2%0G`T*bH!PCj&7&2q$lJ z%huXr#2Xw#OW2V8u<)KKMC8Sds=E8pnf98sX*nQJICDU8EJ38(Kl;?-Cpz|8>3-qf zDl~EOjR^h}Q8e3?sSGT~P~~sa7K#HMDiW=B6Bge?#P~yco9&57NoyLCqKSzSRxx?y z(B4?~IY1B|kl6>v)~}cuQ})o|(3LoIW4AffA z?9RnQZjb2ja@wl)XArXz1z>`yhY85hioEd5g8HYyyZR#q*sS5^oi8y8i zETCh>5fUK%{;vIj@}|Sm{hFFxfl%23TyHPpW*aLulrOcf`QvTnZf%`0xYsvZ-bo(? zs%~xcu5gY`EBs^BXDIY$gR^r6G1w=CcR*Ma1`LMkRwi~Z89|cE7Y8XxSLFfrw zpS^w7-^L^Dk#=_ZGi(~Z00@D~@+p4rQU2)8_@$T4m1sLiTkrVXJI3fnTHVPuS3P&1jtlGmaktgj`3~IP_sLb0@W`?jrvo3~YCp}N z9sN0$p=mKE(^CU#P6IbI=P#QM3cGsY*1Gvx@io8iO3?9l4T5QEB}rY+i1A{5pJ;7# zv7^~qOPmicjX~#cfBaJNoO#kHccXZnD-##^YxECR;)t<53#Csi-Cj%f=u#w|iCZgD zKpqchUqiUcY>{eM1R~{ZHUzwdJt5KO$7<(6nxLeQK1+D6)i7gBkdFr5o!-7XkuZkU z+F~IM++X=yXUf=r#iyy{#tbW$i0ld{{%yQZWJiabWWqtR4{R4f!8L1{#2y6-$j+(# ziTdZ?7!U2=XZKM(^&mzfcim=B+h$N^EekBr>A*5lQg}Knw*Be>qft<*fRp?RqEv+1 zjnQ4kq&4ft@G4;hE17)zYid}vw4!)Ky6{DH4b1_PA{~_-Dz_dKwYwMR&%6cK*sig=*kO*xx;N+r7 zz_?zxm)Ok9?eCGox5*z(^H)#;wr0gDbvcDhYujnxy^>cbs5j(I*Dm_C>s|eETc;yd zVRc@3A;&cqg4ho}{2w(Ec$(y}U#p5Xb%IzfaA1!q!U!bDBg7ojN&@G=LPXnwn(aD? z50A{>2Y!vTT8q4MUMG)xK7k+;!6evoeNlnTb`i6L*(gU>x8q-SIS8T{$cfFN8Xm~@ zy0f7ck_w)sPCeXYq)&+N8Z-X>rfU#yY%uFKR|o`<<=-3%l?jOLAHAPqVFP4+L$i&X zn2%c0f{x6oV`{}((OLcGX`y&7_0`p>izA!6HzbWWek`gMw=eRke-mpPT3noxhNncOk35Dy=|4s4}c*L(I{rL znH6lj-Mj0R@Q@{nWvUBdi3etmr%ZXz^Eo_-{G(+53XuTLgo5ggmvPwN367W~tg-3y{OwBZ|8IYbxWj9pV;9HJng7? zI3T;F*0Z|{{NO$V1}{bnA&sNvZU@3{CeTFG$Uqfo(Be@JyK8iWCbU3lP@yno{8S%j zC^T}>q^4~wmD56c{5_4G$@@CeIPdMLdu(1fE1Oz? zR>(f^R?c!>HqYCS5A0Yv+VrVn^%>f7z#~Y@+N2{Sqz*xl=EtaP+d7reifFp;1U%kg zw(4{VKX>YHN-d4zWeyo9cGCmQ{mxDEApYm-P-?lS;Xhn}5x9~BX7n*)syznyNKZ9F zhNE4w-=XCRJ52g1$^#M(HH4OAp zAwV1zgb+v>(^u{Do&1B0>ss}OvXSQlG_Bep3)*J7c>=Bqn7KE}GnoH|G|WU*W!;e|^a%Z2%5bhx*O~6m#t8 zFQ`Pqt+oH5gPY#CcMme-qE5zX$>4IG-xoPlMgx|Sl#!1r3WU1jYBoe#*(-_ls4PY) z(3fG3(e1*Thz2lixp0>CcFW0rPWtp;0KY&$zdoS`aMo{C0fPPRXWE>5au@%C*Mr(o zXuF1pFd`6Q+W%qZ{Tn<#YfSS;ea{QQBtcbUWqcljpfgqFY5ZwE4>z$wUc>30oe2~# zkVZx>Hz%2JFJmrDwJTol^#T5?iDy4-00NQ~8Zzl+Ykut$5#1fQhPjQRg{XXBrZ}38 zHY+FgXXu{4#c*~RYu1n&oOM?rP~$RvD`>g{B1v`w32aCn_x1YZdpgE&4qUs0!RA!7j#Uisa}|55zjgOJJqb$b|_hR9;OMP5#c9{s4c=> z?1+h8Z)za4&ds>a1?cnOM2AB=&F3Lvl@Eh*7dF=&5sZqavG_YkV{?0wHFPP*%d!Hoff^JR1i>Q)Rlj$a`o*ge5SUc0w=ahi1*1 zcTg)_3|a}+imJ=c1dd-!K197#M)|;=h)v875H!b2c`|loW33XF%};-0ueR>3700NJSV^moc#2`^u6}z zXn1^TECQuw!LTU1>GLR5{r;N+J`WC)>I__^6#f@3>3k85jh;pPGWtB~7J1-vhjKts zH>h8)%}291P6&Jjg`fyoVBk<-*cxal zWV@PWtnmH*k9gi)q@p{Z?wPVeVO}%V>dMi*$v`4=mL-kG{xC2dk*}aX5J0+yM0omn z$?pA!w1S7DIa{SxUOZ1=bQ~WgL?3vw_{V>P25BI{)kK{xtivC=FB}mzLJ2zzgHU)$ zBF(yW?X3BI=#~qc^Q$VD>v_O+*&`#0usq44dmj4S^m*{S0ir}Cnx^sT9nytPX>(a& zQ5+HY;KE9E3DyH<81s0i18*+7__kZz$(_VR8Jm#y4Fy!f+E1F-af#IA^`!~7 z+~%U9S*13#VGuIRqCoTD-YEX@m+i`lk3nEkH1~wItBXL50|-*j)4kHAQq$L0CEYRJ z2HJ^k+OI3@5~30vD++UW=PX`!TrYkfRRU7=C2i12PNO-8Hly%|zpwQlI)Noyu*^$n z!>R^y2TrI%_lG0GZ6LBHozj{mv}-#;lNDSqB;3yL4*|$S<3*9zWcf($zWpPxwiRex zm`tamBavvj%6Dkn(LUy;d4%WN6Uzrw_ac=HgHlOU{2$NyftC2ofCxuS^iW$wL@KTW zLK>d=>Lf6l2ZSWaHcF(+rIa^6i~sY*xIhzR z)w~bK<}936gFyc;M_tafaBau&fs1Nx*1qbicsw>U&!+(s2m<;9R87vvH~bOpgt!mV#e`Ux zRbyyqx&4A23Lmh0bH6+e!geNatqWh*%~&U4^#{*@R9RPBlc`@NjlDUZcWOFDW>zp% z;5(RqLXK)hxj*_3+?l{f90zU9Tn9e1?YRQ`8nrwZXW#gl_m`(|A5Y9Bma7#=9vmPg zC562C=9~RmwrrUa*k67pgaN`yK%4cS6pwdsU`f-k?X+`*S-%{VDPD5BmM?q!nQQld zyLLNJ3HH6luI5(Xz~v=A8&q;>v;jhk`x#MT~_{ezP zl_y(1qVrZ1;Fk;qI4F^e$oX6TSL0tUT$5*o3|D{HE^r6cVdB`OOBZ{$ZrwWN<>i?r zWx^R@C8T&s!WA%k6N@~`j+(j$LswOtKv2o$n7ZuoNjDza@Y7LT!yL2DiNK4XX=r>} zhjjbso8|&}v?Z8~Tvy4%B1?ftqDR}ccQf;ParT7KW|zwzPa5Uxq(7(H0O}{ng;{s1 z7&Er3nA`!guDJ^8JmIWZWja~l-L>1z+16t@cv1zk?QlZCCqlCI;-t&$EoL+mB9kyX zpH?tt(0Pta_~z&({E*D)>$b=z?n>aC6ty|*uh8~q1AfQPG?u_e(&q|RKRTsw-DW!# z1f1K3wyh?ZGSBDpIbloVd0r~LKO9l+jj3sXU@<#r=#Mqd#Nr@=#;G?5< zVti3tyEa^(C;rmimtVjGan_Y2$Ql7^91bd~hqU~Ze?<=*oy5CvaVg9h32ll%LCQNg zvG8{_cLDlQSltfkEf?q$u~Sjc)eGByo_OnXX<{M<8I<26#2j$gUwXdb=Xzzlzj%{! z{GlYyZI(eH=SCv%@G%WTc7dq>Z=HN-22VI1j#KS^k5GUnk_g*Hf`H@|-Utf<2?B)c z#pFCndxsrB<7I}y+ae+k>|OT1k;|WX@zwp`eUFugsjjHFZG(DF_o_MM zv4a0?N#I<5l^JN_sA>6D6Tq1UOW`B#`5^wfA(%iZVnTZb2xuZqW;!=0^OLu}@gMj< zAObFsGgGPlfkq6DAx2UAl3TLoJh|GpA3YBSYCxfFs}Vt-jDR0c&&$iJh&RIqWd>?3 z!R#owSM5SSsDm(=rtz-$xNm=a|C3>>Ccm=uz<2A+{3y^!V5IZt^I&{>htBoF=Wz;X zloqJV>C<=a{#rkHgm99?F1zRU)~e6nDNy^Wu3uwyz^tp}WlGZXQCAMeT#+;68JH{_ z&$+e}Dr^trKh!%d1c6#dfg$s$LiD?Tc6R8jz>Eu5EevfJ1ggFgJVHf=Oy4a2@u?^w z`U`ZblgjlE@)Z|J?YpnZnt9jfCA$?|3b=;J0}*^an#!_F?!No(=4hx0=aAlTd7y(# zYepTAO3oU>4Y<~~q3N76K5pjvY448M^68x2g54%?JdZhHpj+`=z~>QTAAQs7^}y#* z8rD72^2k)i2~@6a~Je%npcnJo7K#!>6I z&*K!(_hGja{r7`EC|~~1gYRQ5Xf7%q@2!FPK_i1y4D@}_1kQQMg7_OvE!U5w) zLvKL7;UmYjqRl8Qaat1isNMk={*sBV`V9Gs`H3Sx80Ktili?u)0bizX5ZDP=A|+e* za$|EA7JTa2GB%ly{1r)rTB{kJ1~>@A)dXGCre)~GcWzRSZ9;;^yqwUs)eQ`^(P46# znwm-;ee}@|xw*N->-ENjwhK3e3xNa&M0((F-FhJ4-h<}ByA$Kz{(kCz9@zZJEHhjc zm+K9Dj{l?CirL5B@*(rf!vkikun*Ha2Uv@ux}A$DJbxI9J!tY%UF}hkZ5wFqtMdVG zqr!l|>x%lzjNb+F*vAO-l8W2ad7uzLzXV*TUTnx$FG(Ere?uK@L=`lK3IYvGpjH8Y zxZ83T{x-w=^N3_2GE-(+nQF$ERqo;>p~#S58k9MG-U-cveiOACOQW0(m^_WX^JKJG z@I2opxc{_PVT5oYkRYnM96@yhZ!ANf2av~q*1i7rLtAFfyAf$nP z?uUW#=X&DvC=F(kq+iZgmaj(h6<$Bqz8Z4!^G2KvW*_N<{VDLiA_-{@ETHOr(4aW5 zuU_{rTq}mD4^9LzI69Y7Z4XG+~K=&x1PSLnKh5{xCm5f@<1ad>=66(NT9JXkAsl4gl+u-UK4Js*v>r#ID6_kdL&8ils2pe zYF}07v1=0Mb-;`}U9cX@iUx_jQ4@hqgked4?V0iZo!iw+86)9p!D(I^=B4=keq4Y) zHL$N$aDTX>kwDHL==0!R3Gp9pc>9Cg@}Mj$dl7XwD4MULnm7HEH?&CueVE(HW3m@a+pp$Lb#pcf-_zqbNAY$g>-OK(yd3DyT63D02D(XGv}h3v^UXMX4M@b@ zASe^2SI`GOiYC>+rb|KSj=Bt7Z$cuxE%3I&ae}jDz5bOmIUBH?i|#f2Q7^{~ ze&_M1p6PzHAD0tw5QH_GGm9+JS>9hJIH>SREawt`QjSd+oH>1iKshvzmG|7%C@@qP z^9CnPnsl+x=c_Y?$iw9=lQHZ3Oe6x@1)9LfD}`(5*~yzGd+gF zVK)C6@@{Gp=s+hPkEcyQ`#^-fPdHo%BnW@#jxYmg9vUyXTu0YWd-v`wv*u-+rAf|4 z9teZw;WUGUU!VUm;QA&JZlZj#VzrTx>B9?*N@>y}IjuuywZw;p=IXxQv-f@7f`gpO zhpGs{YyLIxw?g4r9|0FnL37YEwW5yyj2S%Rsi-SmXoBL;7?^@Z+Bo)H!PlAnGCuq$ zM=w#K({KT+uo*v!qF^-drKNUZRBBk8XwT5$HHzR(+#C2Cjt>OWRgE~~;?&<(ee>7N zGiGn%ZK7b7COPvEv`jb-+gG54_|4gR{SkVoeMq2h=d$daO-xaVV3UYDG2s$_QE@6J zP?GSxm*ETy%{>-)UC}rR_GyQ}--_DRXYc~0YNlR1@TJ&+Z~ZN*m(58f9?QLiHbh_t zY1X4iv-J`l7x6mVgIiMwY8{yOiQIbf}kmJ>i0IArKSHFizl~(EZ^6GcH>c zv5M|Ktc1q92uhtaI(55h7Z+icso2=`8$8OFA1 z$@h5V{$9R<0+=Q?7Ee3}m}3I|Q=n7xLO%>epuk{Q&rYiw&;x=pq-0GhcpwqU!Sv4$P`5tX9nf>8$x3I3QJA`mtg2 zGhRF{&ZT=a&m+%9_rvgBbB*+hdIdZ5^f1ZrK*0ae8RnLC>X``(0tuG$S^4bC$Ne5p zF;{W9o0iYr`IFE8<1=+q4<+BL)#m%fKaW zL)(TcxPVdOionFH%zY16gvSFO&?fT_9@3bmZwqLkwcm**d1yp(I^~j_+zDBG_8#H{ zo~!D8lu>^!a)o1(A0nKBAR*vnc7(UFlb6Vb|P(*Oap zF=}M!h0+*z*O{Lt+|Z4qR0Fa_Uq@)$umV0NC@$jT(`}DMrd)I zrIu{_ON@hu|EGSvR>Mhv>h&_U#5X)J z0SK$HRR>_E>qF1|9GE#*G!}5iO=PC0>%@o+h{y1jk~ITPqP(7=7bT2#clqd}_&=v8 zFq47KDw~ZNM&NlKW~qjL`|YnIg~bK1qg&TZPJXpdf*Ih;FXhUm-HA6bv^kV z)I`wHvtu_W&sj8G`QPfHG$wj_z`1TsM{4=nD+zNl4AZ7Xc-gSAOUB;XLUK9N&S>6i zM1{Z(jik1v0nMYLF@U)X#Lu);o=`in+J&=>tV!@TBoVDB9W;@-GZIF`Q5NLou!Sju zfjBPKG%YqLLQZcIz6cuv!RR|&KVs#xFYgeY4qWB{&#j_|sCr?pL$WW?_UhYmlO zx9@j6^o@qMZv?d}LcG2c{?F$BZ0-Q^ zYt^s!wSwIymSeha#F@ba%CS7dxApeiC3|p(QBG+MEFP$WqB_*kmfY>Wq-&Ppp)jkd zi{{U21G6d`>p5C+&+DBXrwWu}N0#Z!Fan1fhbW4jh!wV5r)FPaLmEz%=PQ_3xCUqhiOtmJVDd7rWvLN13Ij6JVTe~FIlGjxcO36 zQ20J|*!@IR(d>oQIBTO1&IX(WGitXjVc9uY&<|^ZA?fx*pON`_BhCe=cG5`BHe-*P zW9|*w={CfT4-zEF(+z#$vyQejYBs)doT9VEy{_qc_}e zL+AYbeB4($cdEQ$5LO7g!6!wya_v!(KLyMnHOBGu!o7KPz0?^)psj6hl<4ZQ#3>NsHqIcIYT!yY^-fmlI7=3Q-h1@&!Z^ z-{3}G_Z2^=?-<&#t&5XVuqp$5lKl)w$EQO4TK?YhU}u>63$QT(<8!m$Edf2OFBOXOR(EL%&J~n zQyqw-N$@>4Kxj_1{i_W5 zCy2LX0Q|;A)Ayq5c#PWijac_X)xM=}S2nRy69^{>z2Vf+hTB-ar1CKQOcQ_dF5+;>$05|5pZB4XIE6+=6qU*wsfm6m3tcBP!fnV4zF5glWSWM?HUs*Iop1Pamy*1E!U=rs zoL3zL&XiqLq6*VL-5?*`2BaXciYH7I*9U1Q#5k9;`{=6YUs*|T7JwRts;Oe19v4f)2h9QUxD57TU8~a{T1wqO1}%c` zNKmlbq+LYd3ruaKd|{8;h_#ZRqH?g^#SQNF9Y5mg+h}~O2=DQ?&}mQgoqJ0BpU(T6 zwrT72)ZzRgkT-(ARo#6P3N&8{)NVfr*Sy)%zzAmMr&EFN9Jto78dKp4)KOt3A(Sw6 z+bubZUjD{^5SM7x^$LbNk_|2<^-0{d_scMU;KOoW9X6ooS86Imwh#1DdCy;1AU8KR z4#=Z>fILEF5!N!aL!{wDzF>vL7h&WTIlXvY@Cl9p!5JhFtHu?O4BNT4kmct4i728r68ugm z@EDefmQsUwOD+K7nGV{>LatAbbRtQZpob*G={t7w@|;DhmH(|CMq{G?K?FfH38p4} zBS7X@`%2rs^9N)9f&Ekz?D)%yr=$&BK@zbvqp%uNVG5=b1Ea3;59x&ue&PGINJM3~ zg81iPalna@-J9!#-4s~2yRn}u0M2quA4vZH_Jpes1YN!;X70ZG?v{$8w8h%2UzpqfZyFg259e9tEcWX)Od2|HIx8xUCLG0Cb$KL)fiW7!I4y;Hlp8G9u`^z02<-iQ*H8aGov5aV%3WOmU$1|ur zf5{r}gM?Xw;4mcnyR^mIsyxvuqI0^Sj(fo2;Olq^9x~ifU6^`R6 zY=GpUFhd6t;EI_4k-=G?eYjiA!6j8IBo7-Ecpw-3smNYk(!|73`Gy~z=b_CS5dt(} z#c|HCHUatleo|0SFg#GYq`Xf%LjV8(AOJ~3K~$Ss;kXDFgxv$rtRjT`(T$rAdor_5 z2ojors#qvmndX8!F@DeYQ~xt+^CzEW!`&0PHmziFP~YpMVFPCXdBm7zD|C2Q1Qenk z4-qcAG?q{AU>d`4A2H9E;r|B%Ez27m6#+GFoOCjb+k}Z}AKLoD@3~t?yu@|w6brHh z<7Uwj&k?_D_&=QalD7WGVW26kqISE0ghr}k;31RSZNxZybgTOH+QZaLjOIw|0aqr< z9&e|>(zWFgZ6ho&4ambyMWNVG-x!`f_wBt}F0QO+ROIBv*@VnAE}nDRd)rf=l9?Wl zMFYvXji-hPQ?6;`WC@|LLQvc2*|TS)!{N9jP(ZP~dy0{qVU6%35YXgNF#Wdj8=AXs z|1QyCH|>R??fw8XrfC$(W8E8XU%zGM+}#wZAice6zh}&Y(x84bSz&RkDZtO(KbCn_ zRWAVTAemT#+aeJ2Q@Z=)>vIQcGiaJJh-QeSsQ4-Rr>;x6{=J6L7OChPL2{$a?-%6x zU;H3{w%{6g{S=Li@WN+k_QGSF?{Aj}>zD$+!u(wzde9Kb<@epyMUxdNKp>5%07w!p zDhXbuP92s#=dm41wi&Wpk>3MvJqHtsWZtbVP8@l4Px}Se6d5vnr%}s03xd9ceAql8 zpM?d?+9iOy@4owLAP^_cnL}WX31}8P|EiRKRf=#OjObxv9M5kJWTaO~2L^l|f-^oY zd;PR`AKWr?-fmtJ9b8&_!#-x@!_4P@gB2FK2yfc*EAyLH8%mqPcbc8H525QggD5jP z&Jk#_zzlbd$jarkUF*5rRej!}EiOO}$&4T)l7wb0pKt2BFZ*ZAzJl6qUs6fJ3&wN7 z=}oyp>YRZZmZ~i_wGlY3Cd;HGH?MDCMis7ODI9=mAuVM7#eK6r{p(IOTLTrdLh?We zFpYxGwWfSTN)KD=zm97~Hi??W)G9MB8$k%xguy&4M1}oaOE4Yi1qB6H8|9}e;CrSh z%E7?)Us4muD`x#>mZki11t%!NQGJHQ+x&Wa!f|fGC#vr_TscfG$C}38(pJ{wEK4RyMIiHW;uNCMb;dpeUhqes@$wh_R)l!Jz7bV>E zuM>Jvs)LFK)_4kS+lVqSXH6P0_CbFYg??Du5Cj49KlJhA$Gf|9>2gV+eEVr*0Zm#? zr;|SY^wZg@iourQ`)GydfDAa!e_#2{K226M%uxS>&;ve?-QQ3B&xp;R%-YGpoiuda zCv;990-wGZsCR`wd@g;0CM?tpsL-KZi-05j)TfwnPRQZFy9x!yIe<4S`r`6+jI|~X z`!asmfT=XCeH(af))YZ!y0&8%^S$=go9gPd18H>hV(PF9#0rN#KQcDUtO%U8b^9ko zQPIR`_R+xSpS;lScDFIC0HQy9lj505>j7iaL4<24M#ssL!#0&?Nf z#ROwb@*Ph`QTr{#8awAQE*mUh5HgIsk~rrHk@8sRGu*(T$x4>x-XMa?S}m-5jlRSZ zm>2p>?GHh({E*Jdnrob+5lYY|G5Kdrb1sG|kNO zxNhi7&_3b=TEe;a2TmVg79n1FMMoMF#es0g1VC#*SnIo^9|Une=U*Ny-v}O0Q4M0V zJ!IC^8am*1B8pfhMK>MnSQp{jb-&0T&$>a|yzLGe6?w?iMgq!F$T80ZtQyQwN&H<| zzIrWwp9WT0R6O3k)fgpL*THnqXd^g@e6Jjva(&Li&6&D~$Ju8^F(IPm6A_R+zDgW5 z(nZ9-dzqp&MqO1NDhQ7#&PhHfTEFX%T4?Z;3RXl*?E~jsKptFrdOGRVt5?6Gq9O-p zSy(~|1m9=EXPCwi+!vNOcY#@RprXUhK+w+)lr%XPp@*TsXtLrso`>T&{=I(MyEkr` zHSYlLbl6Pt2hMfNl+H|BdZ%Hcrsx#y>)E170?5ir^@8bPaNnSx48Xxx| zKjP}yT)&_O*0S97u0OpmbeP}5uDY0p3X+3>DpBax5(k%EMEf+xo z30s6Mfl!W8)#oS6pL~9{D*i!qf{dU_gfguq(P^t)+Ma88B{#Nw;1`F&EiSpWeo~?iXXYmHVKsY8dt-^i+LC1FxiNJKYO?Q9W6x}m6? zGd70H+_mS^O&`zfx%$N^Gc{SUOihX^i+y?wkcMAXI?p5Y2A(T0Twe!aoG5heluSzd zYtVZ3`+>uRH@o>v=lw?!$Q#kI97=QA`od4STSt7zb?wxasH$RWBv#%C^?x|?zuNkr zda=Tyf6>H*Jm@izoQl>$frICXn@$u>piz+p+V-7~=sR~GA-J}CIWdQgC4vn8YfI1y-a4pHF6RDto z9^ZpOEz+E*EUow9PZo_TI&x?4Cf7|c`|)aHMte$l^dP^B?I;s7QpnalU9Tqhpi zSb#=erUt#un{XGsnRqNgCVbyc0eR#jQ1qQvKM zHfd_b#?9w_e*dE%2I{V=-UmuHspy!|wBX6WD?S8_$!X^F3(=MGl>esF3jo2iuw9KX~S@B)ql{fz= ze?9M1YPSvN8p``bR+f@*crF(?e+_;x{_MbOZ_m`HGWOO_t!$GN~QwiwH9R4pU1>6hV8q0GW!M zOxdzv@yac7jXoQS=W{yNfZ3O86y(V#pX?qAg??i?&!Y(h`MFX?Mh02BbZJRLLxVY& zOI##0fdFb)W`ZQqmG6)9u9-aT+Q!O?>5j}yssw@}3L$chL(D8_vr7pE?wNe&y@Nj; zaqq@ZLn9`3jI)bwOthWXw}<`mizc%AhLg$cib4cn&u_Hlq5Ko zqdAf{B5+a|CY;}Qsd(d%>qt&E(M!ccU|>D=@(Lm^ShU6WEY>AZIx`-ejZVU>{dwxt0(uRAK?bQfMz`&mZesb^aYy)90?0D@uM0mwDI86^(qW zStFP4}%mHn24i~q%i?YcrKJK&*qDaa1$ z4@-E!>dh6cigr@w9BM508#i3K1%Ny%(xpq6t3VPoHZ$Oy;b^b(csvzmlZ3t>%`Hfe z3g8syT81$1xtHoE|NfqVRhxglzt!=_edo%{`F71GQ(sy?{j2`d?;rVk?e-#2y`Gz) z_qxK#qyO2h>{nj=2`7(U`mFpwwXKc>kP$Yimw0jyhra*I_y`azF4ZzX-Uu=CM%ojX zbAmYF!Z+y2Jv#8$2>FF?2qy}X0ofX>BC0A1H{u$MN!cd%m+C7zh?%zt)2Ii!CIpc%%C2l6+ZEN0Y{Qgh3RZXiS zDBGzQ05WcvxnUp6;|d@LzL5}9*#^1Nclz!(7F|#__JL=-3wpmiGe)o_KD+!hgU1$lPZ~Z>iq04bP2k_R{q}O~V@I zIrb|r8)vTPfg5pj$OD4`H{0RJvtN2a7vqS@Ae07)-BxOj<(7Cbqw_{a2p9F6E?j=$ z?}**5>b_kgx^4e~3UgQNXHY+A_qgZ92t!O)VCV^X)JbLpHqTxdDz4YHk2v;gFgnaz zk7u3p$4bq2yq$_@*p%l{g^^wvvjr%bQeGIv2ECl$MB zvk7`JKRkk>2(uBj_2u$$;N|Z#Z~Y~@<`MgbH7~gK!y0_&n6EMAkXW6UXp5F_A{j26 zS4$3th;v*aHFc-aHmzrfS6qAnk@P&PL+YDIqUY?VUf3A$*8eOR;fO7HZ3zLDYC<-3 z)UF9vezi@h<3gIMv25_L31UUTxAF$G4Kd|@eX`udXAy~VZ3GW_vUbkW1{P8Tu2IKh z`B*?hn9ca6BGQW!msF-Uif=c3|NX_~W1kpY`SI^E1;@J{!43kVs32^9Ea9Od2#b=5 zSkjiX%w#Pl1T*n=<0Ck{PHkdsPSm1RvYx(NA*xuWEM`X?C~!En<`(O6Y-p48S0` zYFV(PO7;I>Mmxpq6ZAI1O;3>WZqA@KkM4ADB9N_Kqxs2Svii6>i8(}Ns8MqW_iit6 zO@@S-icELaMn!SEQcI`$Hf*`RV)7lEON0>t zD+YWWC-BaE)EuRUMzWL$BG+Dio-=$p7S(yme)*`@cJ(JqW@^I5&v;d_u{ zm{1_>;luc?y*+6yx&{ZC0`@%=;VgoDazy7joJd4dupiO*hL$8VENe-1FZ5LZdUD#X z*XKN2x8R(q{tctsN}1s>Q!pz!l=D26U~r&wvKtdNk~NR0KaK{Vb=23__ln~@2XvXi zHI4u$n~llfKnIeNKw?7odoV^_PMhuQGylNq>Ra)dc9Ts5M-2D^LSZ5l7Ic+P?VY8Y zp(C0IV<-WF^3wX=p3k%-46CvB$3lJN8CUBYY8NKk&`X#<06%$5&*tq)ULMf{>#MECVMTr9> z4^lU^mp@rI_ZzIic8L9XRAHu|Vd+}ZwQJWiunHlbo#=K%pBFP!>#cJ-of}NeBUZbF z!m$yq#{#h9+a%3C^y*$(P2ElWH706D%V{196p>3bH#=K6@%Wzla~&DSf>6r0-DbO( zf~5t(&zH*zkGn1H)S*8)#H@Twt_4X%K}BuJf8P4oo!@$w8j_E-fI$amMjvcJXhEi~ zK9B>N83aHYSolJ&edNFaP7ZCxtP_r($ys-1p8no@tq1mS!VLoBt?4*7NhPugoU~9= zOGMsD^q0i~Xhh~;hxJF`BN<*bb>c^jKVIBc%vv8dNTeku3h|(*L_@^YdmsB&m+*&P zOe9UoOaxR)~{b*HD=71t%%rCQCT9vkvar|K_e?fC}{I% zeutdo&>qU>fibJk=9)+k{k7qorX&z-AU=N$=7=PYU}*tRA_@v=^f_sdZoj7;+oR?c z)u&8N1tY5L4@kRLuU*Hrk^dl=0YxnJca9CvzuotG-D1%FA6l^S`r6OdV?Wkx^C%st-%r9*!{9Bd^NUh7$(5y3ecX^ys}0W}VgJWLuj~zp@u}=6?(L z=J}PqP&!V^(PXB{aE9}ZYe1q6#LyJYgr6%utMFO9cyr~a>po~)brB~KlxBhH>~PMz zxq-D-q(Z56!~L~ieO9jeRAl~}1fD3+S3d{@0%vmaXl0T|tj^c{BtD<-yQ-=xH5vRL zK+}aqBEw{K@#G#b9St&jKGsb-n!xX=h`b`G7SfAx0uulPv(K2*9-vrR4in^y|O=-;4H4&lLW&*jeI~k2qF{+|w-~rmq1L z59nB|MwllNv8(x^!+i!p0!$&SZ9tP>eI}>>x>fD&TD+p|AC~dIb62(d!{i^^{psDg zg||L=qx+N_Zgrp9YnZz?d>t}~feTsG_&Ml}hBl&GiTLl`S~aC~tZ&uOwo=xd5K~%w zrVkA(gWBnuQ1P@?q20?6My(lH^@#)#+65<&-*SR*lla;j7MjA}6(-5FXx}y!hdX!^ z3+6h}_De6gf;b&HduIN}8hrA@`~f2NI;pGBxg*E|gklh!B<<2k#c)<93W*qG5e$p; z5Wb7@?Y(;S68iP)cS?PIJ&wgSS>kc>csxs! zLGy?pH3$SZgbQlc=pSl669;;Tj~*aY!(k$H>d-+bXp={<=7wR)!nU0l4_l)3Inojs zX=vHT+kOLAx!R&AA&-Z@TVd)8ke!J0ndk_u(@==f{0nmq`~z~S;#K(8X}8*N z*}a*+`}=Uu>5C@iU-!YRf?IlL3wq%M+(?lN;JD`kO$3Dr?Nk(pS52$_sPV_^3dO8V zT$^c;$U+1T=qnI^!$$q5>1#LIz$m!=c;|cMNaXA<4z*M;6dh7M-|4bJrwFpRJG$i z=fPMp-x_~8kWk>#M^s6p|3uoO+f3oSbB54XxjB48raJtos78)WrnxzY+;Z`)2hGdL zF#S3NmU7W<6N;8BtDE`PXNKA`TyH=VQBfNVDG}*`rV21SK^6Dr1qK^#M&vI9mBc$A zJ`ag+T!RpmO3lYgmAa0FqUYv5j*dT+ba?2+4_Xgt$H_)TVZ{TH2z(2C)~~`;baUmW z+qow4`$93>cc7qwqXnEKLXVe(ieKERc#%Yya1xgp>wnPv{QQ%-_Hlx_{zt6-)z<(C zhr{0%6&2~r*d~h>lKcb`xgnz1lMC$ZeU2S_;CE4^uz>jk$LaIB$hkP|MMM&P=CMQs z>Sk%gALvX^=rUOtI_NWD$d!+CLb-*W*!4WVUCCb$7lvN-yl~y1kA-1_)(bk?NZDaZu* z5&s-`S{w{gXvoy10=lIm|C z8)$Ie*{SsQoz>G;W7ZZcXrUzmLV)(sv13Pe&pr2?URPIVi?jX*poj40<>h_jaOfIg ztTqXWR3H%J8W=l?{~uWqu%!S~n!@~k!Pc)&Pnw-+WDp5dVCy!Ov+CLgVzVXk^a8R2 zg%mXs2%bx1`8nZ&bH@r7_n*PH>HPJ(BrHG1-y6h<1`Al-M^8V+Lm)UznS%sRtUj^* zn?>~V`$ztB+1u|AcDHW*GgB0Sp@PP-6EYU!14S0~91)37wOpw`k(0=T32kot_e0rd z>!ne!9zgVmBESuV1D|i=|JBWZ-y?ZGRP;5p;yQ?%6HtX|2DKOet#0lpn_&@3M1iYM zCKW~T`2GHKO~-gl^A+bA0mSjnJMSzv!+aC*dSW8(AW0%bIvY0S7O}_f13Z_ZFp+w8 z??l~h7&B1;SYYLqe$wcxCQ`z8xglGy)Gwtn@%dza-Y&i|{yMB56Zi%h<8r!bm(DQ9 z>;tTiCvXxeUa?YI@y-WdFa7UZ=Vi2R^{M1^h#C@}n{Mh!HZ*nYp$`&5NbnOzeu!49 z1=wz-=DwG5FP-vO_PLpwnFkW@Bz2PElZ`qpNJduA_`giSwT){p5RgYz(?q}k%xrx7 z*T=rqyYQRzDF5bRLhm}e-QG?RgtN_Ku^xVC9|-l`ICt*cQbcUwSahRAMXU=-Hblnw z@#bI5g5r()V*%X{!UBPw{B^{nX6Uq{u-+UREqQ)T1%v?{=-8M(Ar%CWg~Vlxjbyld zWOwBVZR(db1lOaxVR<7_ln9EzVb5V_A}5WNW5@4W`uf|~)R&Y#XwPt0tFkQdhvr!3 ziySK$rTh;Llx5XRvnB`-A%QG;rA%$OVpQHm6GrCr^&s5WL8Z9UVTI3~15A-ddlzn< z+_>i10x280BWQk9?kmG|j1gOqX>+fx`1s9A)sNg7L~}$pZE=7VoK7bhHEPtaxd!4A z;bV;b+^cYydpw>wxw*OJBzBvGMJf7xk%Y#fOXm@MU202fLKIMOc&LC|!`g0%N|E<)7|b|M+MAjed+ekXUrX z1OY?Y5=oM-F!y8jAv#4E_90i^X20sP2l+O_ zKIE!1Z0G9bnpEV9P_CWKd+zVcXFmSSfb#VlA93a61enGU?rVHU@}mtB^r8>$1^pVi zkgc{+Tdg^BPBa0?RY*ZCR~jz;d+ufLjLhy!F!K@Phzex4!=e3&Z>?6Wc%O1pI~?jU zh2@a|eU&XZX{l28%mF2le>L|AmYL;&(XU~stUrkirP7ILL&mWtJZSdm-G$;m01 zG-=Yxs;Vk{R(Gf;3u0ZAg(C$hp{m6sSqTJrACe@n`i9Bs=QID0 z1VbnbuM>yVtEb?4@b2eq7xr6VJL|V^+xqr-nQw2{e)pTN?1L_!Z#%zlKZZOVXeWYb zUqAiJz|5+mnVsR*9%S%E#QIQzg^wI4S0$>mLHTw1Icag*4$TA5e7BndC;c$43 z$Kyc--lWsWQ;9$lX*59#q0H~^WA5vNLi=joPh9ZI2MBOURKRp45~qC;DXSPxLg9d3 ztWs7~vFC|7j+=%~5nAOv&Iz(T^Li!L*vR-sSwllB*3j7B*8iN@?x!Dpj<uSpzIi~gCi}OXdf+WKIE{Yf@k0`8l;R^a= zc=u>x_#vC+nvek>WU|;w%q|{SG47r%a*c|$Ka3J9i3e~1=8LUcx90OD^+pAY;Kk_+ z{=UGq55~2R6|-m0R1ki(Tw3vNgopw)6aZy1;I#J9bf`&W_FB9tXxq8MXzw4!+G}XtR z5Z+B&>lP8e8k-H&?>uG6B){pEQCH!3Qo+K?gVhRI4um7yZ+5s}&wuy{kW?8bfXS-C zY(JWrn)=vT{ghZRpbJb7CjN)g#wB)SFc7&}&-j*7H&1Nwj9-F5k%@Su-_H->4SFCH zF`_rX{=uu8)#5-pp$XA{xS*eT|AZ&rV0hy6!YNxg*d%T*Za}m^hTlER^l-*NFi=eh zoG@B>V#c&36GSGpjzzoi`f4D))qHXJT;tMW*Oiti6GvrV}%P5b19xu_1}=+yEcMTc4PmlXAdyOC5h+`YZAE_yCO8p|@13kr!d!lt^w z6p3is31jw=TUIao*9V6@VZ&p*zBKA^rPJlU8UJQ`8kHu*F9tdq!Wk%j6k#vPp3h0n zkx3B&$Jo{;Y)7sK8yXk++q;xl%xj;U!@2KnSIA{?LR8W6ts@s@K6i+J!>|Ywy?C1G z*_QUt=g@rSzektEEpJcUh_KV^QbT2$sM&6slcw*r3Zqv)3p6GW7&CJ6W0xx8OThBJW`f+^dV#A&nIOvN8VgaqWUDUm+W;@kVgT8X6bS9cQ(0u>? zlpufkiTp)Kn%KSKJ^zM(5De3_#!Wf-fPl&Es(a{nm+*`)0-Y5 zz18NiBcp>&%iG}hd^Jswk&%gp&|d{PuTl7fxA;l4uv*ZRJ_`s!$`p4Y4KM(ac8sT! zo@5nBk|9biS|>vh#Y1hv9rErE`6$q+RFvMgko_QeNVlv0^|w;BQ-HG!#)-Gsdf&G% z-anC{^>obP^xSuR%db=&K4=spIWpERPcmN;=Li=~qs$u%10$PmoY_iu2p^mX;mfbJ zxcER%GbD-n!HQw9v(*%0(jf&^3XW_Ar-+QUXz(`^czB)h_|xCAoXnkoXq2`mrWD&P zw>&=xmY-Tmwx82>wmo}$-*tkFdDcWINa0>gg;#ii&Poa7rBp3Ze;%~2yk54Di{an% zNZrmWC|7>g(@x?&&XxY_={c^bm=yTtziyopKz&h;R{jhpOO8b?8--XL7uM>!d=d?=&vl4MAFplXP@ zdq>>%-Zo`jne>=mV?m#SZqMx5}to1 z%TJbY4^(Vmm4vJWVu9*TIiHF5=A74^ul~l@N!;i$Kf#vmO#OS=_jf5rCj5ZChXV0! zi;nTolHV~K2@vz*t=&>qn(+t@IV(LiMJB}9xxZ2Dp~e`thPQFmdQQOk*S}^p4;hT4 zLJH?u$}Sgy}1uQ*2SXfv_$aQW9Ee7#7q#!q7H#!Rw zQ7WtT%HfYi+liCiHYja41~3oeR4|!B>^pnXwlS2o4b2V zo&wW)raB+fPS+&wADK*18__~NyR>LAy2qJL?!>C!uiLX4$3(tI12eUc*UIzKd~SP9 zNWt~5Jyr9!*ZAyA!FmM}CHuvaVbs8AA80Zxj~#+ILH?b2BJ9aL%2~qy;MB?8!;=d6TPW zZmBQ;q;AXIP9+At6FPcN;o4Pd<~t`7zG6BkytE&O6$&p3TSF~QfhRlK5&CDO?F3}h zJ^&KbLGpq<2Gi2+-+Q6bdvyPBL1mp9o8ld%yqU&zSB!hc=a!YyB8d33IVwG*9FFa1 z-&{cM(YF*oWU<Ry;3En-T!7Mr(1eb~3HXYct^)RfT^t-rAxkLiLEC3;D2Z=% zX8}hjq_-KnqN;*hA=|^VAK9$Yx(M<2J zm%;tBFI)GIwi(k@6nYmh#8#drjXnG2++(%BmeQmAa~lj3zWxm|!5X7d%J$dbWX|5x ziW(2`Fufocu4Dt&gu>P2Uh%M-;YEdQBX)kc3(o)+#-G;-~uf6rrY=3%;|E^&6#Sr z(+nkS{7@O2L7$ft2O&(B(tA$VnspB`;2_9zm~4WmVo7J%VDqexXz@ zi(tM~?!C0W9xV^DT(m_7cMuQc9}Fmq1^vSoBQ~Y=X-5!f2n0`Lo(ji(x}3k8DsX05 zdgBLIkPjwUUfCwQ)VtS~iH&zcU1~g$RqT1S9Zk;dxk5%uw;%@tG4taJSb`z7^+OXX zcjNm4*WOYT>g&)yu=C$1l>p zQ`R(;h;=HKMQwsAg$XbhboBD^ITN3n!cO)JS6u@LcPRCcluqJHs@7jJP^vyRX)So_ z$r@$+3Fyfc6d8_vZnnRuT5shP$ljJI;vKc*Ik7p!Oi8gRp z_A|3GrE7A}yOO58x3eBB9QbZt6VMEyhAhV`o;8Pm^=ZdsBji zTYZGRMewHaCZ9-Iw&m>3Uwl<%n#4%ZB-O0zp`RObTIR98T({6+`%!lRmas(GIK{j7 zzh;pxDY(8lKkze?V(v>dKT&iZB+x)Vh6_J9f9!HS{*z#NKPcGA4<4rE$tcT=l73Mlr0j1ohBv+5LD?>FyZ+;aZr=2Bz=rT>>v(U6%|iYR2Xt#%65$?Wp#X{dUTOmapA_FJqSj!=48y(FZgTC2^~n!_m=a?^d( zQW~f{hG^~|gQYeuMu#h}7}`)vV;W-Ti>R$o^W5fhf$!|2ZIpx0f~=tzgUzZG8%7a# zsgYQMAz&F8$=1fkrGNx>>$e>|Ps*ejX-P?mH83JVE|F0!Vir**O#t+YfFndQ<}Yzr zjU^re?zH@W=6$v_Vi`m1j5-lhB6H3m8VUaF$D^5Kr@KACS&JJWJb-XVUsrF;l+?wR z@BktMIK#JHYF0|_V`y>IYk{LSZUq}bAV^?wEA3B+-L9WHXPQCC?;~@464Ga!ac~=J zy_&l|CsnkOf1F@lz+tCr#__iQMM{Y?iuAV;D`VwcY;p9=-4V8tycnj7D&<#cUn*pd zX-=r$@+^L;T$k|1R$tBM$Mww*hm!E6up}Z;iBA%YBjlUnz3#k+Vq;~^=k=a=AF}@o zOf{92l$gw}Hrl9a)00bN3V|&$Fse+uM5Pu?TUGUs8~(|uL26Gr0l}YiJ33i>@6jo@ zbjls)D|vv?)akGY0f!!T|65wVJ?Xw^{e^5&yDssQnX8{mGi4{mi8u#FAPw$fDWRR& z*4ge`PKZFc;bEVCCzt{^dD6p*Y1j6y%bwDf?q(kBQmSXi-eaW4Iwvt~5zAz<@ZVmV zTE@e7BUU}#*W}kEx>*tAG)c3%O&s~Yz5cW9uGY*xFE^D@QBe&SZr;4M1b(RM8X6kh zUJoa6H{7ef0Mx^5-%&^TW;0X-N? z-~^jmC`pzW1$OPA+PG-dX!s||_Vt>O;qa&eBC7I2Kt;^VE8X(K=h*S!YJ#Obr94LS zzN2LL3N;uVauQ|pVe;+JphvT1<~l!-Z!kGz?1L9fQ7Tb$I+w4*?PO+G{Y6OfVo(Sq zO_q;v`rGYdWtZiX>}TKtDT>-+KJhqwntJ`&T6n}fsKKZo@;MWjpT^OkkqN(lw>(bJ z4iQ@u^UsjYJbV=U){kRo^)i@f8A zlBThuNuq8;l>0@P84VfEqf@f$BoW<0^czX*~u! z_FJ8>*)9&W>K(278Vs#PkG#izfXiAEpVvjNkpmq%3lNEsJWaq`ZH=uDA1PR51)w`_hw*rvl$_JJpra)om??3IIXK+=y|24k zz-xBBp1=CrST`!?<@p-;r&s-*ENMjAVxqvu2Y77eH=_`V=^_ZaFngZbG zHW#eZEL*+S^1YVy@dz3O>rLJf&0czK`b0|e=`$1t&Rd@2q{8}FDWXb`{k*dPdBJ?Z z&z{^UU;^5-95?8wPk~a~pEomV%BJJ6!Bnsq-^OxRBM?44WveNXV@>+xuC3>I`}ax( z>q@orGy0cK+`ko0tDR-?G%B|79{7}#Meii0IcltU&WMN#c!tg!&_DATF(0uf>=d;N zF{+A+il4)H_R-chHmWb?b^2Z|9mQf+XfOdCWRXGbM$SYJeS_iU19hmSWc=R(OJs!- z#&v@EG}vB0ASmb%h=$#6mLHXD=VVuifPE>?B4k})sAgcg`(smWy$3U2z?RD-3Yi2|l%BH37d)uz)p-DWJ$RqHv|PsY~6!u1S6 z)l@|l6&CNwOtvWSz+NM|+LTdAJupxVOeL+tdb@$J9SQWBBKc!85(hL(SZA#46W!?R zRr9Kl@9Xsje;1IunHO0-6HGPI#ZwmZ%pugrePC_vlB=$>Ix7&(9}0kH^!jij5{V#* z%Q;im?Li6;FHh}ysN>(=1kd~fo|3$2)ym!!e(JY^k2Q8ah7a>%^Ka+@w5j;#KCkC1 zU+iUiI9X@aDs#$0-;m?!yzkqw6dKq@EVjA|2>_F_aXujmjHlq-)Ec4yF zom#hl!M0x%v&VZUmNdm-;gasidY58LRl0zOgd^4@%{rA-XnwB+kTg|YRem|$?>9Ls zdUBJ)3{Jc&dOw^v8u$2p+A%JF^y<8CvhaFo-utXK+rpazHx=BBVTd*{MOv~V{Hiuk zi_`9FIFAEr#D_P68{b|jy!!^j=CqxO@{M#7=7A|hk1&r84>y8Ja_zU85O4q@x2+ttu32H0H2w(0%Oca%8W^i?e%oJjr73-lsLi^nUs$FE^%)4)YZ9XM_hqd}e?G#E)WYj`&^y{XE*~ z*CX0rlVKwkewBzfv8S11vy7C)$s9NoQ|I9>v&$k9ZoK4WVvquo&fuBQu{J6Fa_3~)golY3_~(CGRIF%?VI}YBr`f$@52&>wb9pq%XMma z?y{i8SK+1J7*^GuGEo;qP*F_wLx|z@IzA4mYApS#P)}R_9&L-XX(TjfP4-N>BGVS5 z7`7{`VIEPtot6LLKX7W3=kNvG4#ST^dIIulv>ME$sE>R_NiiY*C4eK*r#E4!A#}63 zrK&4?flodxpch@O7r>MT?{d~J@5~?3RZ#XhWL)j=GY7zk?frX^*6F;kyZqyN)3L*d z6%NM}hSV;9ws>D+Qd{aNV<_h71yoWg54#phqNX; zzAp}?yQE)J0I2k_?`m^z?E$3=MvL=i(`nqpYPR}&)woP>T2TrNBDy8H_Z!o+e1jbj zmRSX`BGMt#GT*~_=S@Nu#Q+V4paJ_}YrtX@YIXP988i(wHMgU11fEM!tzP(m(Ae16 zRt}r>lGlqIX8~)PC$|x51Irv%CLw>pZAN>(-R3QB-Ktv#Md<+M)oib{UC%?C5nd=j zbbH;o^E6;ym(0yOve|TqU@gwN;z8{#8Y~8vGVUsymx9sWS0{)%f5cDjz6ddtYD{4k zIt!$$Mo)E32szk;wiqbLP8DMfNJ~yj+aCKTWOSS68g>eIOWkn=Jhn>K%hi~QTYRB< z0B@8UkvVM56EO@(&nXVfv*tqOJtyHAi56~E(Z<**Ns^6Y0y&-yMg0RW#>8Fal~h|~ zVBzphA6aa;O}T>G4reYVX+%|BBTpilZPKKC^B-Jt$w-imfn`NMBHrXPaeQ(0hnVu71B1WF{KqjO*sHir{d@W=@Ai3LVzM1FP%J^UqAm zZ1iKPpS8g&gArVn<$}V+f)~xpO*Kc|6U{c*54V%h?ItTbyz@4$^%+#HHN#1`5xf&R zk2~Q5AxUSh#_s7I;d9?u$`~JY$? z$`i6Q)*SJTHX%!+{0Nezlro+BN!c@ghDX0Z@KEmW@BarH0iS}?mxkI%AiV?n+8h2S zV#v;&z#7+acG7l@K~EYr4GjyK6nFeSzE)7-$bLp`#K8HZG)BR_Wk@=KNgv4M=*nzW z8xavgkN>s_l0^J>-0u>U5a@S0{`4PK!Zq_|Q$4^5hT)t_5u;iK^nW&AM|NxOf6fvG zq0L8^WCG&|oZ`NfA4k7K!ee;PGzWM#?}AwgaEg9Xk`-|Eek3O)74ZS>+aF8pmcs`S zOta-&j1EtkZd(I1F%=s1Dnp_yRqgA}pS3V#ShAR^T3u$S3BRf=2{-p{h+$A8&Ep~G zMWjf(Q^8STKA4kRS*%u?3wLear~bCj;~#c0Gpml|w0x*t%v>pWp#=O8PVDdPM;JX2 z(C2NF2y{G{q0eZ68c&=SUiYg-2W^0$!9CxZdBK4B40Cu$1|$4I|B(Vu-tGi-HdFm$c zBZ%nxYxC&*1stVM7>S1*F~+@CPABzTX0D<_{L*dc^*zh(;NTX<`r)UU@?L?|ylKdl ziW3YF*T*Tc9jOT?{L({@lwvtbLTJL$~SOLlx^C-<&hOb)+#k;`2=)>^k zAxDzlRJ~GbG;sIzaV*etdH;$JEr|p10H%ST7yEUAsW^m5IY-)3-blTsz+GQm84 z&;G9P6mXp#rlkZ<5W0nZihF8alWfZ<@`Ob|7uufHRBfCHO^GO%`YXMiI?wOzw1-}| z6-7zI64SGZV@_fPpS#u#YJP0&kAn~<$!*Ac!N3b40w+ZDGFZQ9-2cY{ETUazm5>kS zfOc_u%@{ulW~^M2Bpfa`SsHXncRp%PqQHh^-X9qeoI2(VJKZ^)qiWou=O|+r#-sa| zMFwmKR}vasvk!IjmWJPHL-ZHi1QtVOkQ~9$m;X&x!8W`LdfXj@pZ@7>e#6zV{c4$} zt-uyS#JWy8%gi{bUglbo<8@Zv0ukJ|UbYJn=Z8cpxR8>Wkx>pz?FH%__lGBE^d)iQ zvL4VmI*G1zaU6b+3XkCYJbPQfNw@9W%6xa{VL9rXZ{}cJ~08?Q#c@K5&280z(%jLs_hW^ z>O!0|sR={9+&WC`C?{fLZ{6i7yr0>!ZLR6J=(}i;>+W@mcfTA)Gh+|Nwq&c|rAQhi z4OB0cP~ZX@qJ(Fcemv~3(nMWI8BKNF3h-JpCbAA zB%&IRO6!quuJ07Jd}6*HcK^X29|0h{`ve{vyJhj_22;-R>c6|0(0>@GtU@6xoCp8L z!D{7#imE00O0AI&CB(NAGS5ppR68j;vY0#LbkAF{|C_j_K%w?1%V|uxrXAR-4E2J2POA?xB@~mw40Pj?WGN2ltBt(jAZUg@OqytEw&x8&;7p-eO^B z>Riv4I(pu(I{s6xJ`<@EW%69H&I1lF&t{&T%k+X% zd4MPEdArFOcGeE|&%txGdXvMdayccx6HeBOb-jp7V#5j6k(1C2)Hmneg#wvKKmAas z;7&fZ%ljj5v zB2CK;6gy8SkCtM}f>x%RJ)@Y?#pB~6Buw8g=rXI|XGL1a!?6_q|FAXeTl$hCWtA41 z5KcrmesH5d)YjWS?Fa4hgn&REQW^diNDr!?3B_q#uiS5^7hcffTd1^(xq~A5^)Iztdg}-P0?q|l$ z>`z~jewg^~aKHTl$s_UotKENIp|ZbYASF|wUJ1d6BHZXv!KfsfhQv=kfoBJX^7gtT z?2bZr-{YOZ|8<-a6BCnUlr%?91GTv0hKp0NHXfPKbJtz^rGde|a8>KPjWN7tZ|($b-<2T7SZm}R@sWOqa3j`r?oU117tM#m(s`;v~~B} zzRcE;J_P!SJx;7kDja_-g`kFD!azbog3>Q5^g4RoX;eo}{5hZkhPBMROI&L$OmTT{ zZ=DnvWy0TG1~bGWq!9@(NZl7|Lnm%s5~fC!{t9ccX1jTL+r365-FuJU5F+9yj4&bJ zVSnub#sHVshL0u{0Xxtc&VS-q2i#~U%_38{_92nD88M~Q7-7Xy(xq|_e>Z8BLT*a( z=!Rh&FhchBjPKsgYtn$q@5IF%?LPwkK^*-clxL+nw=-e@9!aYBM`q$6Ui*UO=ayr8 z^<_JJA!n!Gk>H-VaBs}AND{4hWQAv*#A<9{pzi6~(BCvlEjpO(F6{{+1HSR!LVF=U zN-?7!1*wBfM(`jR;)rm?$w!Y&FVwHAl;z0)kzSVQZXW1C5YTAg>k(Q9$cuUYI@N4v zZHzPAh3h5YHxZ!&M#{>{X#G~fx7jBa&~(KnBeA&;NFEyitU!F~+9N24)~fg!N|o)@ zV?XESX6I#^#@d*)9&<`@W<#<}YAj>G2n&LfAhD=4!d-l{6Zzh44&U65o-cIko7G4h z0U7p~0R9j&_B3lEqDxY!2~AKrVwE7Btpu3II*;d{ZGqN_FHK7hB&+mz_!)_!tD+~* zE4vQK%&OiIQv=LEwKF1=dbj?=Fh33TXM z!xOR046PVPyYL*JUgzbXF&s_g!XKe zM?Whl;Sxqybc)Ucoi=s@2f6sju;}`u-_0@@C#HSj%XWTUzmDo$u)^M&Sc>LEi)G!a zz694`ENyHa(%oyUxTF8#qU9 zD(sILG8evdd!0!IfDKAefT~pkS4mQb!F11b!MjMNkR}D!1BA?(qdlBldOZeamMj_< z2@#9lURQ~{uFza1$|*5$31`sIc;V&IqfJE&Bz-uJeQXKz zvmG!OOG#V&2l@y!5!f(PK=drJX$aqX)#< z)Qz~m7=FGXZPDxBICK8*CbKp)nfCy}k@^C)MU{m;y^b~*i)N+?9?g3uEp7U=Pcjx; zAE=ZN)jMRtmL`=1Rq98vl#1zT=ybVdce` z;H5NLsUaUrDZ4etF~9V`CS-zk8LXg)jN}W0+8I?z5Nfhs>+{OaJw^ajeYEBIjB*p$ zycb==Dd#;1AF{1j!YK>Um=@Fz?_=Q#Y}-~({8*u*&da%)%+3JKoY`zfAHtp&9z#w{ z2&HvB3I}E_%v|<;a?id(I!RFWJL2{~(Om>C{*XY&Nf<>9d;$@Opep z+o@gCMAT2SH6ez-HNROYJCwBQeOh_nv+@b2qM2DFU_YWxDN4dNwBHZGZN;WbiFSJw zCK8#YVC3;xbsXwn3kWL3mnNU0L{SDVP96=IdnFqRs|pGNf!spORU3Bvs8e%Qj8IcE zvqYsL@yEe#ttY-qBUDKeQrbK;Un)eV(_cf#?&16yZ$*T&OROaPHa85*ihD(> zk0}gD*X@4<9XP_Gy_BG{3n{1tBW5b@LZcPrdPbBkdrZM-C$o9ovVbX8n1?T^10;kW z3OofZn3OEebLgDPpPCHc2v==6j%!_PROi-v zj7z8(Q6$`p5S1;qFJ}zMHxxM|CQQG7auc5Z(PtppMA6=J1ms72?=SHfER664`EQK` z?Qa7Or?4mHTDu%9pKq3LaXx1OIXDV-NG}NoW2z&vy(m9thLg+I<~dAAoLi^umU(on zKr-A~hvd?qPvi7d7;)$6B49$%YbWaEhzpP^YJp~E%&wcm@4j)L+g)d=f58GHcd4?f z3qkASBl5kP;_viY9};xWvSUi@PZR!zc+du(bu38Pei5haZ>N@lC^zQ5QvtPfbdYsH$b?ZyMKU>6p$_a1jsR~31LEIS z81>Qjh5%ctY;lxG*F5*!Ymx8v^R=i9=O9rrx$+SAiUD&{mKbiaWeHpp^6U>J(|rXV zLoFS7ggs!*yAr6{prp!V7Q&Z(sR>`4pTF~xB+m_Nn_eDS%vMDx$(mn5OZ`Q!r}X!d z{vI_vrx-ReT}Fv9GHE1k?6;am2!*WY_=9KTiw!cR8EMeXpZ3+J{g3nTCTw13r8BZV zUszlLi2}v34Zc#Np~|5O4qB2Z0XZmD6%bpnAv7SuMW)$yy?N-=#Te&`V~Y`(e-Lqf zz3k^`=((yPeP>rNP$V8nz9UI;;?#u(sjKdpH*HCi5VSrS!-3q%a%lB!3tcwHrKAYx z<7aYS@^mwx#uPXhN5S)E2lW5zmlxZ;YUf%+2g8GE!RBkg_sO%SN?&Su;SvvYoD6Ql zVm>kdLi}zkI%{y%rz5*PmGT<78ipDeyLRjFJqHcT)K_G}M>1btUUrYdU;iV9+fNcd z2tf)KivH=1_7jkosTf(NXyQMf^*5j1aX~k|AFKkJ~>r$!Ipwp^>5Z*;a&*s-*V15adZVWh<@L#-|&v142T|CBl1h-w|QQ;U5>D zpY&A7v``}h7Og{+I((%pK9a{p01DlFK+zHsECgbB&9s?!KiWFO+F{aZ#dcqK#_)s_A%6KHRL59*ZJ`+@^c}=3N7RjVc8Sk{aX)qMNyF9 zF#d8-#}MHHwfVO@Z4|5aB zLtlV&lnaJK3*@?xFY|UUNso(uzD3LK6bDA1#p1JR3*xBq{raG8`vBuX1q#nZtacZO zW%QqF2B4>bO#<~m<&Dn#@>samXYij_9mw8InF|b$3s>&FxHvHxGei}xg*`< zWT2*2=KNX3G^z6LiraYtb7;HS?&^mG{f#diKnGSApRb4kqu$S!P`DG0Zm-<^eaoGB z`kHChDvE(E3Xf)zPYL26Uz;gdSv%X$UfEQbIC3Z^qk#Nz-{YTlOjos>bIb_Zt5i+? zjIb)1LHS!8c9TkDJh-y7lXt156!7c7pP;1b4MnBSA!)Yp^%`3v7$@b`zA=fp$Gg?- zsCRZtEDy4cdC%Obg6^NUYGD0UHu6$dN8MG@6n3^Kw^QLM#-|xK(Aq?yO=%DstT`eI zU{%aQh84PXwQl$Svi2;Z1RGkxG4z22FSOipZ!1{FXH1LQh+ zS?%WTfk}oJs-u`+#%|MK8zJTiwh0AFRRYZ*Dw~1rtM6r+zt`IPWx$m36P_D&2xkwB zq-2P=p%rAp`{F`3qu=n2^U3#34ifdx%i@h=Kt%>yfHh!Ss(rpnYTJAlTai~dh{%Th z+2k>g-{JL!{=gP~(!`QZWM2DInp%(oCYupMO~@`nafM>pZ}Hb3*h@G-NudKjHaQw> zsLO2sI&~?EKqjg#ir_|G z+C?OoJeuaE0!)Tqyus>PdKP*+Ks6$$x4d7aUJ$``D7o}V%yW(Bp}-@JdJR(AZUc$Nr*cnH79eQSY<@Tf zZ+ZLDZF)KX$2}^+rlb-Emw>=FyVXM3de7I}Ia9WS4RxxeqzXRWuJNt~s65YsWUtSV zl-Q`f7y$>u>A#P@_fUC)H|M_1j8I{>J>&of4;EaCLVi*@+n#q^zEASn^o7u;;GtJ47S>+ESXqH>16wT&#$um6b#g(3t-m zm7C8$)irXj7W;#z8(TVvWE>imevI!P>lUi0z%{!VL?h zaAd1nRe*X)3Q&E3y<&*qfQp6&D^Vk1VfsvwEE|dG+WhFsI|!BWFQh-_ea$K{uH(c4A;tVFFPkUc8+Q|_KyT8K}z5%+-Lk9(tHi5wc$7Lbh!?0h!Q18OU4kbD6D$BNZLTO!xe}TAZTG{=l-si=EMy9 zxHIU8Kdb3&W&s5a{jGlHu!G6mzl@soSF*B7|L1h}Gt2j+oNk`M-;Y)bG{mqCB+khK z%i_a<1q>XHu~vf!TMDLrpKxz^a-mXAL882u<)Ml!8Ce3-Rqw3 zub1}r_CONZ>i4WVR(T>}H{b~Ib-Rwg{z^)-S1_-lO)~JB5Q)-^ODIAiSXbwS9aS^c z@lIMP1Ca6*%Zg4p%gCprL49Dtq3R!(QwP}L#9wPK>cbMl?EK4ckR!3%gFVRbmkW2- z_NIaPd5cS8-MELxmz{L=V|06*qAP|j%+i(DOiW`t2K`SVpKE0n9028u^N;~H)R5{Q z5_GKavMk9C8;&EEKm^xR6p+KO#1H8A?Y*x><_s*$795R0C6#}FJ9) z2xAXGbmV!TGTyydDk^#6Y8N1?Uih4ogXj{b2a80YY!}0wgxNrgg!4|-4MF$4ex}3c zbBGxzwaoN6ElJyYdfAWbG0@WbDU;X;2z1Cts~MF#3d6JSbnK)ZZ?(1b%qzU~#!?0) zf+joqF$5D6L2DAsr9b1$m|tR;6Z4WaOL3QB&@HmR7FLxtPJ3tJmM@|thCOJMFv1jm zHKvGBl8*d{!qgb2`7D^{wR-t9nm7;4rXhhr2J}ujBYS$r(-a>kN;G@I^v4~+>?N(H zB<6`3RkdD#;O}RCEXW+e)@CQTrJ~<{EIEd#5s>g$v^X>75)~C#_kSVa+z&J{NQxb? zmW7QoT7j}Lftt03ePFYZ!BUB)c^vlohIa@5@SL2T9ckM$ki?WE&CE*87s{0W7y@M0 zL|dewH6e`qf*|301Pz#@(MP1={PR$U@nqmZA*ur(m^7N3UB3cy$ydlqf2 z;~`McYo$)Jj`!*O@1vGTIfxiyTK{k=nd2`11Udv6`Ss*wyUa=TbORljm^_I#cV-yUJ=rCinSg`;kM;f=NPlnlur3}j#)N^81j5i1uGX3=p{*nG z>D662RK>ORs{Zfc2nm__Vq#)n4xF6_Ah4letg(tz3mrhy!Dd<;oKsiE`rlp)BXHs) zC8njd2r|TD{wao~eu4__>ui|fu9Pq{qZl0_K>@B*Q_{Q_gM6IX^Ek&H%aLT3wAxtk z+0&y-pV{SPrU4EfUTIQ8?vDj6&_Mv#YUF?N)(@Js@rAuMD~%)PEXEUsZA7X7uakmmpLaz67&v?n_=c0l3^oqP9guiy zVEX%C2|A98i%VZkO-YjpD=Pn6Qbc6nR{_dWfi!?6Rg4G=h!-FlC6g-jNy;}eWWR9X z{xPBcA5r1H=lk<|GY}lwZ=j{-Am5Ip=MP7aqAW)*egwbINkKUCf1T7tU|D|T*5oEwv;q80aYx(W zwb62Z3YX98fqhGUXTn{H!{6qfqK=JR0;d+Suu^)%k<~BBY+-i z28QcQV8)r-#}@6@)YSBLk>)RdbjfHEi{iLMyAufgr4B64 z!h=e?h)I|-o{Rdkrv&6dBLF&1pB&^T5qm~nu26r4ARTsgm6IcJBW}bQswYZoeAo8&$m?+6>ELxilu5k zZU&H5jwdo2&_`qoGO^ZR-arYQ_G37Xwj2f!#Xj0uiq_Kw{SXhejRt~|`8`(mhN9AL z{1QpDO{n)Ip)-MBj)Y1CXNIvEY}jA$331zYoGUZMeO|@V3exT{lP}b>k1K93kD^ym zSxL$TmyjvL^rwFk#)8ZkxV3)H1bJ!-nvE8-=fJX>?WMl$iw>ByfANJZPtZ@5(#~@S z&af<4*7rxgMwYK`+x_7f(mda%)mb(+HjAWsB(nef80Q${C(h`XKOz$5tjfX7gi1i}bzPcUT^>j@c)Afv zjOqTdD6ps4?9|6;2E4gyl2cRJ1<{#%&=YjZt?E&;<^MeOK?Aano!X9WeJwC0X``WF zG%U8eKkszxyB?Y4$(o3~{?D(@m58#ccAFjvL|mY$I(Q_{nkRcivN)yUMIty8&ygovoMP=7|?1gSb)%G$PTy5v%VVi zW$dVs$M+#huur?T!_-h8w1BTog>hL(R@WV%`!9g)ogmz4aNyPPTKX{|0Gv)U{`l&% z2@en7vElo?N%{WyWpLZ<`?9T`Fx{U9ngOpRhEU+&3RELA&CQ(;ytV^k7D<IdGxn%}NP&IW`vT*>^p-6z>pLJn9h=^u8$bLf80gFr zZCc_FzSC+|Wh?ogyD8Kq~&wup|44iaJ#^w-yWFf=kD;inn(t|ZhSNO()wQ8b$w zj9Tld#^(&%fpEvCRde@)h6fp7knhI6eT@XR+s|54T1v489eH3O8nGvtC-KSqVt+xm z0lUoY)~;L4^k1B}0gOxic7ajyDloF!b_U`S++Bg-@U68q?M-a((0?DfB3#HhvM6Ud zii$IedU~IeKn2M^^0~a82+NXBW=JZ;^L5L+2SS6yQ`gqt{vZ1Q7S10$ZCGf}-M3GO z!1H-N+yb|8t@?mE)ad}ckT2j@bB9epKrrusx?ZXlSSy0KHyS7dP}T4d4PZ2hQ^m)5 z{$Chh$GY_CH3wi#S+wJq37}guGqo)MQsczm=_Roc?E;X+frvf8OIOmw#3UZR_Lax& z!ejq1%|QRqf%7+q?OMpw3*SWMfG%ku*WYIq1-?g)uhqUlK@c>;AckCD0m7&BQ9(`M z4K&-D+K_;L#QF7Z^7T6HpGpF;5~5!U&!1vSie{lYQ`-7;ZGq_Ouzz<(v^p5&;hsfA{+6?Wb<7mTqYa%T|7 zg;?`?1%OZ@0Vr4ETAx_VhJIt7d-SkgtTn;gl6Thpcx)-`rf}@L=6mDudERsvU@{$z z|FbJ2Y#`o3D!vogdp$m_@<*QU5Eu^Fr#}nSFBYwnv&Fg)>D0!f^-d7XVd6p=!Zq3H00AH_fuYya zlaqgUaYvs23(5K+@6&(;7@FZYK+m)J!+GCpfW=o&*FADYz4r4<)Q2K6^{7G=B8iR) zDDnAAQJpApA=kXxJ^tZBw)dCP^@h=zJY{KT>Xa(jmK1P+UWs|BQ|6b2d`8Q94%(qd z*9a>k8_jX@p4?xQSqw2S)&#)wyWX{SR@x<$5q$mZG0T1Z>bstI*R|JkNA9)PQp1v0 zm-|hq=bf*gUw2As>XV1Q{#H+cD_!5OY*-Df4zwuWfZ=nnK%-r>jqKZonBuD|$K z;}5bT3`H#h#q4*b*439lngsmx)YR7{@Tuy(S)TEhRQv-UQ3)un%%cWuOTZY34ZpgGy?clN&Y)S^0af!LhfTR$?Q>84=pX&bH)$W* zUQ+*i7ye3@rB?Z@^fQ6JWO}WCw{OGwS z%BX}dowL!s6QJ}THsnEk&F7^dDLi0V&I#7 zwYi0_nfQNx9CF2mQmO_3)6WPxz7X zaof6QHW%IYImw~~tz&;s73Po#BhutiLC;aueQNV+(@*P(mBYkG6eGDq3gDcFJsCMJ z)pw|QFmsy$O__(BP23I%9^r))E*G>dN!9}h&Yl8JYz%=wl#r4H+VNcyS~67PlOw^B z4B42X&Wi#{)X;~&Uvf)ZJpG_AP8kHqqsF&`B}nF;u3XD=o*k(<-OdJx2vDo!{R@ZI z-iyA$&Y;5yJ3t^ftzXy#Mc|+vsiNT8FaW4ql_FemDn{0-z z+e`|(AFY%lgfkg<7-DH$0_KP@4}h{k^j7$}S4DFmh%_#Du`v-nwo=-3c*y!;T{~i* zks;0HYZMBF>fBxG|LbgS&Mso$9xMKr9FI{!5?1TsI(c)i*O355?)_1De6sSR@O<{? z4hqW|adEnx*7j(+?N+c5&>w7)Qd3iNp78gprzYQALo6E2i04Xc1`4942tQPFm#2M> zn!gJ)-6&BD12=Ts$`JPaemPC6rwnkCsHFZx2 z&?9WwWAIl#o+=sx#B)FhU;`kak|;*&06^^*qRW+9L{;Pdd6cn#X+Uu&1OvYA@2|F8 z{}65^9MIq-2Smy5qzs=>2ZlTei*dK+7Qyi2sN#P6vCvfiO;lexPKeM@9W<_?e- z^h+Mud`WG*Ks|v_AnE}Kz16h|iC_?7G|_qcmOxCT&f;ShgbJMDEtJzj1qe zYo${9WpO!AP3jcvNM0~j@l$<0RH$O`7GA_iC|5oF z`dacmyv=r1%oEP!!*Nd6h|_(9njp-?D_jmOONI(vQuu(M^z2XTbD5W`t7n9o8$Spm3UBIL-d}r0g2;8J!%hoUqRm76Y`< z9jgJbpcOo}`DCZ}`XgOW(f;VspH>argFk=H-x4sf|CIA)o>48X6w;SU?e)N_4zbQ> zZDcc6yS>Wcz#wIndF(Iu8rs?Biiczo675&Ov@ygxR9NZW_V+ia*oWPnDzuplROxnK zyb2ScBZKM45IVA{pbg-wuhin`j)8cAyvpu?%l5yFL=|an<$W<^$KX~FRe32DY?D`zHLE1kBjU4U*6BcG)tovvq z=26GOF}cZnE<4l>A4*mC*ygCE%K&+VBVdLE8C9O=1Ud8`h#z+lD**LI@>ss6&l2i3 z$-ilkUG}8-9`RYqf$&iU^FhGGEAln`M{GzBd%H*~OM3i`jP<}q3ifJ{;_}V2UGQk>VhtgAPE#CSaQHar{18>Kdl*VmxxG^Q?Us%3quNKI;8BkOp6d~9 zp8Q)OFYi-ZP)4iBJUzjeNV9!_z2LO7wZ%5Tb6)3(Q0o*-1~jKMuwCo^e))@C*miBH{(v3EuVty=DYQdze|Erej9xl?`tMSB54uW5XTxN0D| zUlCV8Gc&PrAiT2i-7#^`hKc%l2%FahI`!Xxtm#ZB>6nI0zm-M&+P^b%N3!QZ;hU}c zreSSwUog$iB7m7qIkyHA5)#gL#a3=s2wqvz-04aq3o}JMr^j>g{^Qj)(TA7|^Dpj; zJ&s266=Y=WoB|vXt`4C41Vfb9OLi@w)?0^YZaR87k;YGtOkID+w=%b8G_XbP%p~XSGyhAO3FO5h|OmysW z+ivP=9(=NbK+S5O1P}dZ6G03v_J*|nlA~HFPm7w>B;39wnN);yz&tyU{Lfo1QFZAj ztw)EQ&+4$?5k#_OwxQ-?N0Z%@teVDx1z+Ok8`CIrg{`=hI7!AdMF^?YXGzo z=NZ78F-zb&(tU5H{x)mh6iAzuzHyT&Tu}XUY}e3K>IPJiQFWb}lXJ$|+S(fNXK%Fz ziqLl(F%BABXE!%NJb`UB&%h#28jRw4HX=VpZsb-Fseuzg>GoicY%@SY=^P8=Lal+kr_{^XXDE%@Zd72>#tT?cR;j0Zlp}ZNnum1_d8h>qj4jXlLe*~X^$}| z5^QB@DF=FMT0%3#6lsId;mBZOWi3G$Sqtfg)UueInw>pWwc5VRT6C34r-z|G`X0SJ(3{028c2O2twZw_m3oZIs8%I40%FD6|KoezMLs zadYsK5Q_Rh+d0}T&>8>Rz^#A8$LRQY56p5g9-5uy792}%ECaf}^plRy4}ri4{kt6E zx?D^ki9&^dz`a{ZR`%GEiDP;6h^?0XinwT$D4U@F75}CrVP?JC5zs3s&Zdx3Z}40w zQi_#KCTEIDR;9!jI&EZEoQg>qv7CbekFYHRn@Ll1a%=1@JG_qEyn_|?V!2P6ouQl^4t%7zhzVuLxCX~dZf^!1h9 z?9x(lz!ZIa&PXdKgRakyx5hQ1l7|w-z4A7+jb?_=2=#y3%P|k`+_c6ztQS0na;~u) zh_wHn19T>vdO$uh6%=#lA1-wNR-i@ER983m42_~vZ{8`Qwq?oa0V8oDj+y?h~t zH#(ZQ8N{b}Z-D6B2HdEy079?p+n>i^@OXSNkTiM9;Zm~|{&v;kzJsILdb+Red3-_} zw`p6IPU3wNW8xd8_QGF3H?It5)_nO0=clFsE9UMf%G1TfFB=#fQnLkufMkbD# zl{KH*P)AQmNl8INL!+*$N_kBF;5t~ZO1rUvVNmL4A~R8I7)WiM;(!(oy`oW0!fso` zJFV2w!BxO=e|Qs^$xUX#Rpt3*xL|A#( z!r#S%y4z7oUdO}CthW-N_56VNAVNk~cI<0v8C7!pWJaPmQxpWe63{bh&fBM6xz)pq z6S(Ro3KJI-6Y~bPAu~Tef7uYZlq{^9=j`G{V?1VJ%H}RA?6uAbFzefS%gaHi2lmhx*h8LlkSb~9>h`_={T(5C`pEnPm5|USb2$7ZD~+Gh z`ayFl?9Yldc<&XUJ#5$xQ^%(pqP50Z?hY8%lLKcA^vIhFvGgF|Gw;9?X9kVm=L8%w_0%KC^&WgUFK%Xhyu7@8 zL3TmGO*n?3JwhbVSU>Goh79Vv9)g8!*OyIN3f9aoA^sA~%|*>c2VbGTbkLiN-u$Cd z!u%qZ!>{#vdtI%l%s}SC-`Y1faXP`i;zsWBEqpPDPG-{t)L+IOqmIt5r}S% z@5{0D`t(|5j+B2! WFQeN_hJg{WRM(9yjLHq}Mg1RVI{;4r literal 1356 zcmV-S1+)5zP)BJB*86ilEd{TUxgx)N{M!5neM;V|jNqg50Dxc+U-66`XC7#HCrhE+ltB#Y9>k>BfIm)D<@3dUA?6nOV05DYSY*twds zGTUt-CbDUs7an&m1Sw6aHn)PMu>in5hUTh|vgt}Acf6r8r$V?ZrUqL_s7J!Z+Gjhr&jjD)BCfuY?v#n8I82;gh z8}6ET$IkstC_ER2lv1rEhq}S`+ZMaCIQ6|x zR%s(@6w;w70r64H9=#e@*5VO1w|hvJU_h{L=tM*7~%ZUgmMUyCt|QyX-@rE=twy2xbBJZD}@PR=B2}t zz<|ST;CBD*Uw=DxygxOW>>IIdOwCBwEYmbCfFuYyz&4bV0o#B!+hpq*U&q#qJj&A@ zu{bw1H9;H>%;Oyk-x$7my=(1(&W=J|(+i_LJ>${w=aTg7@!g6UPDnY#qvsh^ZnFJ0 zgGWGsVD#Sm3e29rU_r8=pn$}awY30%rj9GF6it_8IBjLwQj=Qcp5`MFHqCPY=@N9; z0nK&`=MFHJ#+B9H*t@ghzFRy1(AwGxfo02pk`hgm`d%v*GlgLQN=5Yv{!S*bc<<@c zc}~cfPdN*cP^QV2R}szi-6(&qY;HFolc(scO==CfO?ideIWkk)cz~l!4I2LiD+pc5ZHI<(4qUruCKqB zHo?sl|Dy6jd}~#)Oi!N(B3m{lLJWFnX>^!)c{P6C&E?%t%Btd~(wJEjBXK+!;MzM**OwrbaI< zEj2@-&;ec7m$bCBprWEe5gw=QFH%$XXO0NPb+7IfN-3WLrjX+2LC68n4d6f^5a

- Bulb-icon + Bulb-icon {{_ "Knowledge_Base"}}

diff --git a/packages/assistify-ai/client/views/app/tabbar/smarti.js b/packages/assistify-ai/client/views/app/tabbar/smarti.js index c5fee1513c21..f17d44e72e0d 100644 --- a/packages/assistify-ai/client/views/app/tabbar/smarti.js +++ b/packages/assistify-ai/client/views/app/tabbar/smarti.js @@ -60,7 +60,7 @@ Template.AssistifySmarti.onRendered(function() { type: WIDGET_POSTING_TYPE, cssInputSelector: '.rc-message-box .js-input-message' }, - lang: localStorage.getItem('userLanguage') + lang: localStorage.getItem('userLanguage').split('-')[0] }; console.debug('Initializing Smarti with options: ', JSON.stringify(smartiOptions, null, 2)); instance.smarti = new window.SmartiWidget(instance.find('.smarti #widgetContainer'), smartiOptions); diff --git a/packages/assistify-ai/package.js b/packages/assistify-ai/package.js index ea1d5ded0202..e80b4f320348 100755 --- a/packages/assistify-ai/package.js +++ b/packages/assistify-ai/package.js @@ -49,7 +49,7 @@ Package.onUse(function(api) { api.addFiles('client/public/stylesheets/smarti.css', 'client'); //Assets - api.addAssets('client/public/bulb.png', 'client'); + api.addAssets('client/public/assistify.png', 'client'); //api.addAssets('client/public/assistify-beta.png', 'client'); //i18n in Rocket.Chat-package (packages/rocketchat-i18n/i18n From 80b7a913c86126629b3ece57c447e131feb51268 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Wed, 7 Feb 2018 17:48:03 +0100 Subject: [PATCH 06/26] SmartiWidget: Add new conversation search method --- .../server/methods/SmartiWidgetBackend.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index 05d40ed52708..51d61850e0dd 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -1,6 +1,7 @@ /* globals SystemLogger, RocketChat */ import {SmartiProxy, verbs} from '../SmartiProxy'; +const querystring = require('querystring'); /** @namespace RocketChat.RateLimiter.limitFunction */ @@ -85,18 +86,30 @@ Meteor.methods({ * @param {Number} templateIndex - the index of the template to get the results for * @param {String} creator - the creator providing the suggestions * @param {Number} start - the offset of the suggestion results (pagination) + * @param {Number} rows - number of the suggestion results (pagination) * * @returns {Object} - the suggestions */ - getQueryBuilderResult(conversationId, templateIndex, creator, start) { - // TODO new api -> client-side pagination? + getQueryBuilderResult(conversationId, templateIndex, creator, start, rows) { return RocketChat.RateLimiter.limitFunction( SmartiProxy.propagateToSmarti, 5, 1000, { userId(userId) { return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }`); + )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }?start=${start}&rows=${rows}`); + }, + + searchConversations(queryParams) { + let queryString = querystring.stringify(queryParams); + SystemLogger.debug("QueryString: ", queryString); + return RocketChat.RateLimiter.limitFunction( + SmartiProxy.propagateToSmarti, 5, 1000, { + userId(userId) { + return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); + } + } + )(verbs.get, `conversation/search?${queryString}`); } }); From d7e135739305c9cd2efd2c60696c1a91b1fd77e6 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Thu, 8 Feb 2018 12:00:47 +0100 Subject: [PATCH 07/26] SmartiWidget: Add debug logs --- packages/assistify-ai/server/lib/SmartiAdapter.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index 82cc03ca0571..e022778abca5 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -48,8 +48,8 @@ export class SmartiAdapter { //,"private" : false }; - //SystemLogger.debug("Message:", requestBodyMessage); - //SystemLogger.debug("User:", user); + SystemLogger.debug("Message:", requestBodyMessage); + SystemLogger.debug("User:", user); const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); let analysisResult; @@ -65,8 +65,8 @@ export class SmartiAdapter { const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); const supportArea = helpRequest ? helpRequest.supportArea : undefined; const room = RocketChat.models.Rooms.findOneById(message.rid); - //SystemLogger.debug("HelpRequest:", helpRequest); - //SystemLogger.debug("Room:", room); + SystemLogger.debug("HelpRequest:", helpRequest); + SystemLogger.debug("Room:", room); const requestBodyConversation = { "meta" : { From 84abd1e4eb4cd693d20d9c042e1f54c2270562f0 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Thu, 8 Feb 2018 15:50:21 +0100 Subject: [PATCH 08/26] SmartiWidget: Remove unneeded log --- packages/assistify-ai/server/lib/SmartiAdapter.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index e022778abca5..a1dd12360ae5 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -34,9 +34,7 @@ export class SmartiAdapter { */ static onMessage(message) { - //TODO is this always a new one, what about update - - //const user = RocketChat.models.Users.findOneById(message.u._id); + //TODO trigger on message update, if needed const requestBodyMessage = { "id" : message._id, "time" : message.ts, @@ -49,7 +47,6 @@ export class SmartiAdapter { }; SystemLogger.debug("Message:", requestBodyMessage); - SystemLogger.debug("User:", user); const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); let analysisResult; From 5b0ae691a21014f268d0a19d8c3285e375a10fea Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Thu, 8 Feb 2018 17:31:41 +0100 Subject: [PATCH 09/26] SmartiWidget: Fix double encoding --- packages/assistify-ai/server/SmartiProxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assistify-ai/server/SmartiProxy.js b/packages/assistify-ai/server/SmartiProxy.js index 587b97e6114e..3c0a7e0c4374 100644 --- a/packages/assistify-ai/server/SmartiProxy.js +++ b/packages/assistify-ai/server/SmartiProxy.js @@ -33,7 +33,7 @@ export class SmartiProxy { * @returns {Object} */ static propagateToSmarti(method, path, body = null) { - const url = encodeURI(`${ SmartiProxy.smartiUrl }${ path }`); + const url = `${ SmartiProxy.smartiUrl }${ path }`; const header = { 'X-Auth-Token': SmartiProxy.smartiAuthToken, 'Content-Type': 'application/json; charset=utf-8' From 8c1bb836b6002d582ff68e92240dce02d68e58e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Fri, 9 Feb 2018 12:39:34 +0100 Subject: [PATCH 10/26] Minor adaptation for 0.61 and linting --- .../client/public/stylesheets/smarti.css | 132 ++++++++++-------- .../client/views/app/tabbar/smarti.html | 24 ++-- .../hooks/sendMessageToKnowledgeAdapter.js | 2 +- .../assistify-ai/server/lib/SmartiAdapter.js | 54 +++---- .../server/methods/SmartiWidgetBackend.js | 25 ++-- 5 files changed, 129 insertions(+), 108 deletions(-) diff --git a/packages/assistify-ai/client/public/stylesheets/smarti.css b/packages/assistify-ai/client/public/stylesheets/smarti.css index 2648fee43ebf..7d36d90f6deb 100644 --- a/packages/assistify-ai/client/public/stylesheets/smarti.css +++ b/packages/assistify-ai/client/public/stylesheets/smarti.css @@ -29,60 +29,78 @@ position: relative; height: 100%; display: flex; - flex-direction: column; } - .smarti #widgetContainer #widgetWrapper #widgetHeader { - position: relative; } - .smarti #widgetContainer #widgetWrapper #widgetHeader .widgetHeaderWrapper { - padding: 15px 15px; } - .smarti #widgetContainer #widgetWrapper #widgetHeader h4 { - font-size: 20px; - color: #3D3D3D; - letter-spacing: 0; - margin-top: -10px; - /*margin-bottom: 30px;*/ - } - .smarti #widgetContainer #widgetWrapper #widgetHeader h4 img { - position: relative; - top: 5px; - height: 30px; - margin-right: 4px; } - .smarti #widgetContainer #widgetWrapper #widgetBody { - position: relative; - background: #ffffff; - width: 100%; - padding: 10px 10px 10px 10px; - overflow-y: auto; - flex: 1; } - .smarti #widgetContainer #widgetWrapper #widgetFooter { - position: absolute; - bottom: 0px; - background: #f4f4f4; - color: #747474; - opacity: 1; - width: 100%; - text-align: center; - border-top: 1px solid #CBCBCB; - transition: 0.2s; } - .smarti #widgetContainer #widgetWrapper #widgetFooter .help-request-actions { - margin: 0; } - .smarti #widgetContainer #widgetWrapper #widgetFooter button { - -o-transition: 0.2s; - -ms-transition: 0.2s; - -moz-transition: 0.2s; - -webkit-transition: 0.2s; - transition: 0.2s; - position: relative; - color: #747474; - text-decoration: none; - padding: 15px 30px 15px 30px; - width: calc(100% - 20px); - font-size: 14px; - margin: 10px; - text-transform: uppercase; - background: #CBCBCB; - border-radius: 3px; - display: inline-block; - text-align: center; } - .smarti #widgetContainer #widgetWrapper #widgetFooter button:hover { - background: #d5195b; - color: #ffffff; } \ No newline at end of file + flex-direction: column; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader { + position: relative; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader .widgetHeaderWrapper { + padding: 15px 15px; +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader h4 { + font-size: 20px; + color: #3D3D3D; + letter-spacing: 0; + margin-top: -10px; + /*margin-bottom: 30px;*/ +} + +.smarti #widgetContainer #widgetWrapper #widgetHeader h4 img { + position: relative; + top: 5px; + height: 30px; + margin-right: 4px; +} + +.smarti #widgetContainer #widgetWrapper #widgetBody { + position: relative; + background: #ffffff; + width: 100%; + padding: 10px 10px 10px 10px; + overflow-y: auto; + flex: 1; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter { + position: absolute; + bottom: 0; + background: #f4f4f4; + color: #747474; + opacity: 1; + width: 100%; + text-align: center; + border-top: 1px solid #CBCBCB; + transition: 0.2s; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter .help-request-actions { + margin: 0; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter button { + -o-transition: 0.2s; + -ms-transition: 0.2s; + -moz-transition: 0.2s; + -webkit-transition: 0.2s; + transition: 0.2s; + position: relative; + color: #747474; + text-decoration: none; + padding: 15px 30px 15px 30px; + width: calc(100% - 20px); + font-size: 14px; + margin: 10px; + text-transform: uppercase; + background: #CBCBCB; + border-radius: 3px; + display: inline-block; + text-align: center; +} + +.smarti #widgetContainer #widgetWrapper #widgetFooter button:hover { + background: #d5195b; + color: #ffffff; +} diff --git a/packages/assistify-ai/client/views/app/tabbar/smarti.html b/packages/assistify-ai/client/views/app/tabbar/smarti.html index 046dc6c68fe3..28c77e4668f5 100644 --- a/packages/assistify-ai/client/views/app/tabbar/smarti.html +++ b/packages/assistify-ai/client/views/app/tabbar/smarti.html @@ -12,7 +12,7 @@

-
{{loadingNotification}}
+
{{loadingNotification}}
{{#if isLoading }} {{> loading }} @@ -42,20 +42,20 @@

{{_ "Knowledge_Base"}}

{{#let helpRequest=helpRequestByRoom }} - {{#if helpRequest}} - {{> HelpRequestContext helpRequest }} - {{> HelpRequestActions helpRequest }} - {{/if}} - {{/let}} - {{#if isLivechat}} - {{> HelpRequestActions liveChatActions}} - {{/if}} + {{#if helpRequest}} + {{> HelpRequestContext helpRequest }} + {{> HelpRequestActions helpRequest }} + {{/if}} +{{/let}} + {{#if isLivechat}} + {{> HelpRequestActions liveChatActions}} + {{/if}}
{{loadingNotification}}
{{#if isLoading }} - {{> loading }} - {{/if}} + {{> loading }} +{{/if}}
---> \ No newline at end of file +--> diff --git a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js index bbb2c880ae6c..c681533fae94 100755 --- a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -19,7 +19,7 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return message; } - if (!(typeof room.t !== 'undefined' && room.v && room.v.token)) { + if (!(typeof room.t !== 'undefined' && room.v)) { return message; } diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index a1dd12360ae5..c4e5d36078ff 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -36,63 +36,63 @@ export class SmartiAdapter { //TODO trigger on message update, if needed const requestBodyMessage = { - "id" : message._id, - "time" : message.ts, - "origin" : "User", //user.type, - "content" : message.msg, - "user" : { - "id" : message.u._id + 'id': message._id, + 'time': message.ts, + 'origin': 'User', //user.type, + 'content': message.msg, + 'user': { + 'id': message.u._id } //,"private" : false }; - SystemLogger.debug("Message:", requestBodyMessage); + SystemLogger.debug('Message:', requestBodyMessage); const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); let analysisResult; // conversation exists for channel? - if(m && m.conversationId) { - SystemLogger.info("Conversation found for channel"); + if (m && m.conversationId) { + SystemLogger.info('Conversation found for channel'); // add message to conversation SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ m.conversationId }/message`, requestBodyMessage); // request analysis results analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ m.conversationId }/analysis`); } else { - SystemLogger.info("Conversation not found for channel"); + SystemLogger.info('Conversation not found for channel'); const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); const supportArea = helpRequest ? helpRequest.supportArea : undefined; const room = RocketChat.models.Rooms.findOneById(message.rid); - SystemLogger.debug("HelpRequest:", helpRequest); - SystemLogger.debug("Room:", room); + SystemLogger.debug('HelpRequest:', helpRequest); + SystemLogger.debug('Room:', room); const requestBodyConversation = { - "meta" : { - "support_area" : [supportArea], - "channel_id" : [message.rid] + 'meta': { + 'support_area': [supportArea], + 'channel_id': [message.rid] }, - "user" : { - "id" : room.u._id + 'user': { + 'id': room.u._id }, - "messages" : [requestBodyMessage], - "context" : { - "contextType" : "rocket.chat" + 'messages': [requestBodyMessage], + 'context': { + 'contextType': 'rocket.chat' /* "domain" : "test", "environment" : { - + } */ } }; - SystemLogger.debug("Creating conversation:", JSON.stringify(requestBodyConversation, null, '\t')); + SystemLogger.debug('Creating conversation:', JSON.stringify(requestBodyConversation, null, '\t')); // create conversation, send message along and request analysis - analysisResult = SmartiProxy.propagateToSmarti(verbs.post, `conversation?analysis=true`, requestBodyConversation); + analysisResult = SmartiProxy.propagateToSmarti(verbs.post, 'conversation?analysis=true', requestBodyConversation); analysisResult = analysisResult ? analysisResult.analysis : null; } - SystemLogger.debug(`analysisResult:`, JSON.stringify(analysisResult, null, '\t')); + SystemLogger.debug('analysisResult:', JSON.stringify(analysisResult, null, '\t')); - if(analysisResult && analysisResult.conversation) { + if (analysisResult && analysisResult.conversation) { // update/insert channel/conversation specific timestamp RocketChat.models.LivechatExternalMessage.update( { @@ -106,7 +106,7 @@ export class SmartiAdapter { upsert: true } ); - + RocketChat.Notifications.notifyRoom(message.rid, 'newConversationResult', analysisResult); } } @@ -122,7 +122,7 @@ export class SmartiAdapter { // get conversation id const m = RocketChat.models.LivechatExternalMessage.findOneById(room._id); if (m) { - SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${m.conversationId}/meta.status`, "Complete"); + SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${ m.conversationId }/meta.status`, 'Complete'); } else { SystemLogger.error(`Smarti - closing room failed: No conversation id for room: ${ room._id }`); } diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index 51d61850e0dd..34a786a07045 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -1,6 +1,7 @@ /* globals SystemLogger, RocketChat */ import {SmartiProxy, verbs} from '../SmartiProxy'; + const querystring = require('querystring'); /** @namespace RocketChat.RateLimiter.limitFunction */ @@ -21,11 +22,11 @@ Meteor.methods({ getConversationId(channelId) { SystemLogger.debug(`Retrieving conversation ID for channel: ${ channelId }`); const m = RocketChat.models.LivechatExternalMessage.findOneById(channelId); - if(m && m.conversationId) { + if (m && m.conversationId) { return m.conversationId; } else { - SystemLogger.debug(`Smarti - Trying legacy service to retrieve conversation ID...`); - let conversation = RocketChat.RateLimiter.limitFunction( + SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); + const conversation = RocketChat.RateLimiter.limitFunction( SmartiProxy.propagateToSmarti, 5, 1000, { userId(userId) { return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); @@ -33,12 +34,14 @@ Meteor.methods({ } )(verbs.get, `legacy/rocket.chat?channel_id=${ channelId }`); - if(conversation && conversation.id) { + if (conversation && conversation.id) { let timestamp = conversation.messages && - conversation.messages[conversation.messages.length - 1] && - conversation.messages[conversation.messages.length - 1].time; + conversation.messages[conversation.messages.length - 1] && + conversation.messages[conversation.messages.length - 1].time; - if(!timestamp) timestamp = conversation.lastModified; + if (!timestamp) { + timestamp = conversation.lastModified; + } // Store conversation ID and latest conversation timestamp RocketChat.models.LivechatExternalMessage.update( @@ -97,19 +100,19 @@ Meteor.methods({ return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }?start=${start}&rows=${rows}`); + )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }?start=${ start }&rows=${ rows }`); }, searchConversations(queryParams) { - let queryString = querystring.stringify(queryParams); - SystemLogger.debug("QueryString: ", queryString); + const queryString = querystring.stringify(queryParams); + SystemLogger.debug('QueryString: ', queryString); return RocketChat.RateLimiter.limitFunction( SmartiProxy.propagateToSmarti, 5, 1000, { userId(userId) { return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `conversation/search?${queryString}`); + )(verbs.get, `conversation/search?${ queryString }`); } }); From 01213c284f69e9505d57f66e0b90114de6a6db06 Mon Sep 17 00:00:00 2001 From: Peyman Aparviz Date: Mon, 12 Feb 2018 11:25:06 +0100 Subject: [PATCH 11/26] SmartiWidget: Use legacy entpoint as a fallback on new meesage and on room close --- .../assistify-ai/server/lib/SmartiAdapter.js | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index c4e5d36078ff..ff4001fa87d1 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -50,13 +50,25 @@ export class SmartiAdapter { const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); let analysisResult; + let conversationId; + // conversation exists for channel? if (m && m.conversationId) { + conversationId = m.conversationId; + } else { + SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); + const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ message.rid }`); + if (conversation && conversation.id) { + conversationId = conversation.id; + } + } + + if (conversationId) { SystemLogger.info('Conversation found for channel'); // add message to conversation - SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ m.conversationId }/message`, requestBodyMessage); + SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, requestBodyMessage); // request analysis results - analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ m.conversationId }/analysis`); + analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`); } else { SystemLogger.info('Conversation not found for channel'); const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); @@ -119,10 +131,20 @@ export class SmartiAdapter { * @returns {*} */ static onClose(room) { //async + let conversationId; // get conversation id const m = RocketChat.models.LivechatExternalMessage.findOneById(room._id); - if (m) { - SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${ m.conversationId }/meta.status`, 'Complete'); + if (m && m.conversationId) { + conversationId = m.conversationId; + } else { + SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); + const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ room._id }`); + if (conversation && conversation.id) { + conversationId = conversation.id; + } + } + if (conversationId) { + SmartiProxy.propagateToSmarti(verbs.put, `/conversation/${ conversationId }/meta.status`, 'Complete'); } else { SystemLogger.error(`Smarti - closing room failed: No conversation id for room: ${ room._id }`); } From 2952531bb5b6b2fb80c99e04d63d3b2748ed98d5 Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Mon, 12 Feb 2018 17:42:05 +0100 Subject: [PATCH 12/26] End-to-end tests for Smarti 0.7. First attempt (not stable yet) --- tests/end-to-end/ui_smarti/00-preparation.js | 8 ++++---- tests/end-to-end/ui_smarti/01-integration.js | 18 +++++++++++++----- tests/end-to-end/ui_smarti/02-cleanup.js | 2 +- tests/pageobjects/assistify.page.js | 8 ++++++-- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/end-to-end/ui_smarti/00-preparation.js b/tests/end-to-end/ui_smarti/00-preparation.js index c33c515a03a4..b05f23b7a4c5 100644 --- a/tests/end-to-end/ui_smarti/00-preparation.js +++ b/tests/end-to-end/ui_smarti/00-preparation.js @@ -1,7 +1,7 @@ /* eslint-env mocha */ import supertest from 'supertest'; -import {adminUsername, adminPassword} from '../../data/user.js'; +import { adminUsername, adminPassword } from '../../data/user.js'; export const request = supertest.agent('http://localhost:8080'); export const rcrequest = supertest.agent('http://localhost:3000'); @@ -71,7 +71,7 @@ describe('[Smarti Connection]', () => { .set('Accept', 'application/json') .end(function(err, res) { clientid = res.body.id; - expect(res.status).to.be.equal(200); + expect(res.status).to.be.equal(201); console.log('clientid', res.body.id); done(); }); @@ -98,7 +98,7 @@ describe('[Smarti Connection]', () => { request.post(code) .set('Content-Type', 'application/json') .send(clientconfig) - .expect(200) + .expect(201) .end(function(err, res) { console.log('post config', res.body); done(); @@ -314,7 +314,7 @@ describe('[Smarti Connection]', () => { it('Logout from Rocketchat api', function(done) { console.log('authToken-o', authToken); console.log('userId-o', userId); - rcrequest.get('/api/v1/logout') + rcrequest.post('/api/v1/logout') .set('X-Auth-Token', authToken) .set('X-User-Id', userId) .expect(200) diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index e8d1eceee8ab..79e3871ae5a1 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -3,7 +3,7 @@ import mainContent from '../../pageobjects/main-content.page'; import sideNav from '../../pageobjects/side-nav.page'; import assistify from '../../pageobjects/assistify.page'; -import {adminUsername, adminEmail, adminPassword} from '../../data/user.js'; +import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; const topicName = 'smarti-test-topic4'; const topicExpert = 'rocketchat.internal.admin.test'; @@ -15,7 +15,7 @@ import supertest from 'supertest'; export const request = supertest.agent('http://localhost:3000'); -import {checkIfUserIsAdmin} from '../../data/checks'; +import { checkIfUserIsAdmin } from '../../data/checks'; describe('[Smarti Integration]', () => { @@ -32,6 +32,7 @@ describe('[Smarti Integration]', () => { assistify.createTopic(topicName, topicExpert); } catch (e) { console.log(e); + browser.pause(1000); sideNav.openChannel(topicName); } @@ -39,9 +40,11 @@ describe('[Smarti Integration]', () => { describe('Open Topic', () => { it('switch to GENERAL', () => { + browser.pause(1000); sideNav.openChannel('general'); }); it('switch back to Topic', () => { + browser.pause(1000); sideNav.openChannel(topicName); }); }); @@ -66,6 +69,7 @@ describe('[Smarti Integration]', () => { // assistify.answerRequest(request1, answer); }); it('close request', () => { + assistify.clickKnowledgebase(); assistify.closeRequest(comment); }); @@ -77,19 +81,23 @@ describe('[Smarti Integration]', () => { }); it('knowledgebase answer visible', () => { - assistify.clickKnowledgebase(); browser.pause(1000); - assistify.knowledgebaseAnswer.waitForVisible(5000); + assistify.knowledgebasePickAnswer.waitForVisible(5000); + browser.pause(1000); + assistify.knowledgebasePickAnswer.click(); + browser.pause(1000); }); it('post knowledgebase answer', () => { assistify.knowledgebasePostBtn.waitForVisible(5000); assistify.knowledgebasePostBtn.click(); + assistify.clickKnowledgebase(); + assistify.closeRequest(comment); }); }); }); - describe('Cleanup', () => { + describe.skip('Cleanup', () => { it('close new Topic', () => { console.log('TopicName for cleanup', topicName); assistify.closeTopic(topicName); diff --git a/tests/end-to-end/ui_smarti/02-cleanup.js b/tests/end-to-end/ui_smarti/02-cleanup.js index e1194787bb62..417483a63eb5 100644 --- a/tests/end-to-end/ui_smarti/02-cleanup.js +++ b/tests/end-to-end/ui_smarti/02-cleanup.js @@ -24,7 +24,7 @@ describe('[Smarti Cleanup]', () => { it('delete client', function(done) { request.del(`/client/${ clientid }`) - .expect(200) + .expect(204) .end(done); }); diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index 339c617c895e..3f404101e19d 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -40,8 +40,12 @@ class Assistify extends Page { return browser.element('.external-search-content .smarti-widget .search-results'); } + get knowledgebasePickAnswer() { + return browser.element('.convMessage .middle .selectMessage'); + } + get knowledgebasePostBtn() { - return browser.element('.external-search-content .smarti-widget .search-results .result-actions'); + return browser.element('.button.button-block'); } // new Topic @@ -78,7 +82,7 @@ class Assistify extends Page { // Knowledgebase get closeTopicBtn() { - return browser.element('.flex-tab-container.border-component-color.opened .delete'); + return browser.element('.rc-button.rc-button--icon.rc-button--outline.rc-button--cancel.js-leave'); } get infoRoomIcon() { From 0fefab5811182287065f07e2919ed63133d7006c Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Tue, 13 Feb 2018 11:19:34 +0100 Subject: [PATCH 13/26] Added delete topic when finished --- tests/end-to-end/ui_smarti/01-integration.js | 25 +++++++++----------- tests/pageobjects/assistify.page.js | 8 ++++++- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index 79e3871ae5a1..34c27a1509d8 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -7,6 +7,7 @@ import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; const topicName = 'smarti-test-topic4'; const topicExpert = 'rocketchat.internal.admin.test'; +const shortTopicMessage = 'Das ist das neue Thema zu dem Anfragen erstellt werden und die Wissensbasis genutzt wird!'; const message = 'Mit allgemeinen Anfragen verschaffen Sie sich einen Überblick über den Markt, indem Sie Produkte, Preise und Bestellbedingungen unterschiedlicher Lieferanten und Dienstleister kennen lernen. In einem allgemeinen Anfragebrief bitten Sie zum die Zusendung von Katalogen, Prospekten, Preislisten und Produktmustern. Wie kann ich dieses Wissen nutzen?'; const answer = 'Das ist die Antwort auf diese Anfrage!'; const comment = 'Anfrage wurde erfolgreich beantwortet'; @@ -29,22 +30,18 @@ describe('[Smarti Integration]', () => { describe('[Topic]', () => { before(() => { try { - assistify.createTopic(topicName, topicExpert); - } catch (e) { - console.log(e); - browser.pause(1000); sideNav.openChannel(topicName); + } catch (e) { + assistify.createTopic(topicName, topicExpert); } }); - describe('Open Topic', () => { + describe.skip('Open Topic', () => { it('switch to GENERAL', () => { - browser.pause(1000); sideNav.openChannel('general'); }); it('switch back to Topic', () => { - browser.pause(1000); sideNav.openChannel(topicName); }); }); @@ -52,7 +49,7 @@ describe('[Smarti Integration]', () => { describe('Message', () => { it('it should send a message', () => { assistify.clickKnowledgebase(); - assistify.sendTopicMessage(message); + assistify.sendTopicMessage(shortTopicMessage); }); }); }); @@ -64,9 +61,8 @@ describe('[Smarti Integration]', () => { it('create is successful', () => { assistify.createHelpRequest(topicName, message); }); - it('answer request', () => { + it.skip('answer request', () => { assistify.sendTopicMessage(answer); - // assistify.answerRequest(request1, answer); }); it('close request', () => { assistify.clickKnowledgebase(); @@ -81,11 +77,8 @@ describe('[Smarti Integration]', () => { }); it('knowledgebase answer visible', () => { - browser.pause(1000); assistify.knowledgebasePickAnswer.waitForVisible(5000); - browser.pause(1000); assistify.knowledgebasePickAnswer.click(); - browser.pause(1000); }); it('post knowledgebase answer', () => { @@ -94,10 +87,14 @@ describe('[Smarti Integration]', () => { assistify.clickKnowledgebase(); assistify.closeRequest(comment); }); + it('close request', () => { + assistify.clickKnowledgebase(); + assistify.closeRequest(comment); + }); }); }); - describe.skip('Cleanup', () => { + describe('Cleanup', () => { it('close new Topic', () => { console.log('TopicName for cleanup', topicName); assistify.closeTopic(topicName); diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index 3f404101e19d..401184f8009b 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -82,7 +82,11 @@ class Assistify extends Page { // Knowledgebase get closeTopicBtn() { - return browser.element('.rc-button.rc-button--icon.rc-button--outline.rc-button--cancel.js-leave'); + return browser.element('.rc-button.rc-button--outline.rc-button--cancel.js-delete'); + } + + get editInfoBtn() { + return browser.element('.rc-button.rc-button--icon.rc-button--outline.js-edit'); } get infoRoomIcon() { @@ -180,6 +184,8 @@ class Assistify extends Page { sideNav.openChannel(topicName); flexTab.channelTab.waitForVisible(5000); flexTab.channelTab.click(); + this.editInfoBtn.waitForVisible(5000); + this.editInfoBtn.click(); this.closeTopicBtn.waitForVisible(5000); this.closeTopicBtn.click(); global.confirmPopup(); From 3e2cb4caf3415bf689a053c3168acfe4db10d79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 13 Feb 2018 17:11:24 +0100 Subject: [PATCH 14/26] Show custom room types icon in channel header --- packages/rocketchat-ui/client/components/header/header.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/rocketchat-ui/client/components/header/header.js b/packages/rocketchat-ui/client/components/header/header.js index b8bbcd65ce55..5eece208faab 100644 --- a/packages/rocketchat-ui/client/components/header/header.js +++ b/packages/rocketchat-ui/client/components/header/header.js @@ -8,7 +8,7 @@ Template.header.helpers({ avatarBackground() { const roomData = Session.get(`roomData${ this._id }`); if (!roomData) { return ''; } - return RocketChat.roomTypes.getSecondaryRoomName(roomData.t, roomData) || RocketChat.roomTypes.getRoomName(roomData.t, roomData) ; + return RocketChat.roomTypes.getSecondaryRoomName(roomData.t, roomData) || RocketChat.roomTypes.getRoomName(roomData.t, roomData); }, buttons() { return RocketChat.TabBar.getButtons(); @@ -32,7 +32,7 @@ Template.header.helpers({ }, isDirect() { - return RocketChat.models.Rooms.findOne(this._id).t === 'd' ; + return RocketChat.models.Rooms.findOne(this._id).t === 'd'; }, roomName() { @@ -66,8 +66,8 @@ Template.header.helpers({ return 'hashtag'; case 'l': return 'livechat'; - default : - return null; + default: + return RocketChat.roomTypes.getIcon(roomType); } }, From 983bd794e45c5d68124477ae9d87ea9ca8249f52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Wed, 14 Feb 2018 11:50:05 +0100 Subject: [PATCH 15/26] Explicitly check the room types before sending to the knowledge Adapter --- packages/assistify-ai/server/SmartiProxy.js | 1 + .../hooks/sendMessageToKnowledgeAdapter.js | 5 +- .../hooks/sendMessageToKnowledgeAdapter.js | 4 +- .../.app/package-lock.json | 874 ++++++++++++++++++ 4 files changed, 880 insertions(+), 4 deletions(-) create mode 100644 packages/rocketchat-livechat/.app/package-lock.json diff --git a/packages/assistify-ai/server/SmartiProxy.js b/packages/assistify-ai/server/SmartiProxy.js index 3c0a7e0c4374..b3dfddd106ae 100644 --- a/packages/assistify-ai/server/SmartiProxy.js +++ b/packages/assistify-ai/server/SmartiProxy.js @@ -39,6 +39,7 @@ export class SmartiProxy { 'Content-Type': 'application/json; charset=utf-8' }; try { + SystemLogger.debug('Sending request to Smarti', method, 'to', url, 'body', JSON.stringify(body)); const response = HTTP.call(method, url, {data: body, headers: header}); if (response.statusCode === 200 || response.statusCode === 201) { return response.data || response.content; //.data if it's a json-response diff --git a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js index c681533fae94..d297de951104 100755 --- a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -1,6 +1,6 @@ /* globals RocketChat, SystemLogger */ -import {getKnowledgeAdapter} from '../lib/KnowledgeAdapterProvider'; +import { getKnowledgeAdapter } from '../lib/KnowledgeAdapterProvider'; RocketChat.callbacks.remove('afterSaveMessage', 'externalWebHook'); @@ -19,7 +19,8 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return message; } - if (!(typeof room.t !== 'undefined' && room.v)) { + //we only want to forward messages from livechat-rooms + if (!(room && (room.t === 'l'))) { return message; } diff --git a/packages/assistify-help-request/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-help-request/server/hooks/sendMessageToKnowledgeAdapter.js index e01acfd6d941..5f8e72b4249f 100755 --- a/packages/assistify-help-request/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-help-request/server/hooks/sendMessageToKnowledgeAdapter.js @@ -14,8 +14,8 @@ Meteor.startup(() => { return message; } - //help request supplied - if (!(typeof room.t !== 'undefined' && (room.t === 'r' || room.t === 'e'))) { + // we want to propagate messages from requests and expertises + if (!(room.t && (room.t === 'r' || room.t === 'e'))) { return message; } diff --git a/packages/rocketchat-livechat/.app/package-lock.json b/packages/rocketchat-livechat/.app/package-lock.json new file mode 100644 index 000000000000..44135eb613a6 --- /dev/null +++ b/packages/rocketchat-livechat/.app/package-lock.json @@ -0,0 +1,874 @@ +{ + "name": "rocketchat-livechat", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.3" + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "autolinker": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.6.0.tgz", + "integrity": "sha1-utN2t62OQV8i8QL8Dzf2QOZPHL8=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-1.0.3.tgz", + "integrity": "sha512-pRyDdo73C8Nim3jwFJ7DWe3TZCgwDfWZ6nHS5LSdU77kWbj1frruvdndP02AOavtD4y8v6Fp2dolbHgp4SDrfg==", + "requires": { + "nan": "2.6.2", + "node-pre-gyp": "0.6.36" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.2.0" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-js": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.2.0" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", + "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", + "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "5.5.2", + "har-schema": "2.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "sntp": "2.1.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nan": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz", + "integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U=" + }, + "node-pre-gyp": { + "version": "0.6.36", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz", + "integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=", + "requires": { + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.2", + "rc": "1.2.4", + "request": "2.83.0", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "2.2.1", + "tar-pack": "3.4.1" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", + "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "rc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.4.tgz", + "integrity": "sha1-oPYGyq4qO4YrvQ74VILAElsxX6M=", + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + } + } + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, + "request": { + "version": "2.83.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", + "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "requires": { + "aws-sign2": "0.7.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.1", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.1", + "safe-buffer": "5.1.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.2.0" + } + }, + "sprintf-js": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", + "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz", + "integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==", + "requires": { + "debug": "2.6.9", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.3.3", + "rimraf": "2.6.2", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "toastr": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", + "integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=", + "requires": { + "jquery": "3.3.1" + } + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "5.1.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", + "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" + }, + "underscore.string": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", + "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", + "requires": { + "sprintf-js": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + } + }, + "wide-align": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } +} From 097a2a3548985a80031377be689cfb108bcb7561 Mon Sep 17 00:00:00 2001 From: Rupert Westenthaler Date: Wed, 14 Feb 2018 11:52:54 +0100 Subject: [PATCH 16/26] Updated the SmartiAdapter so that the delay to establish the channel ID <-> conversation ID is established faster to reduce concurrency issues if messages are added shortly after the first one to a new conversation. Do prevent such things completely one would need to implement some kind of locking, but this exceeds my JavaScript skills. --- .../hooks/sendMessageToKnowledgeAdapter.js | 7 ++- .../assistify-ai/server/lib/SmartiAdapter.js | 43 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js index c681533fae94..056b77670dbc 100755 --- a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -27,10 +27,13 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { if (!knowledgeAdapter) { return; } - + + SystemLogger.debug(`Send message ${message._id} to knowledgeAdapter (Meteor.defer()`); + Meteor.defer(() => { try { - knowledgeAdapter.onMessage(message); + SystemLogger.debug(`Calling onMessage(${message._id});`); + knowledgeAdapter.onMessage(message); } catch (e) { SystemLogger.error('Error using knowledge provider ->', e); } diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index ff4001fa87d1..67c6104d2194 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -49,7 +49,6 @@ export class SmartiAdapter { SystemLogger.debug('Message:', requestBodyMessage); const m = RocketChat.models.LivechatExternalMessage.findOneById(message.rid); - let analysisResult; let conversationId; // conversation exists for channel? @@ -59,7 +58,8 @@ export class SmartiAdapter { SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ message.rid }`); if (conversation && conversation.id) { - conversationId = conversation.id; + conversationId = conversation.id; + this.updateMapping(message, conversationId); } } @@ -67,8 +67,6 @@ export class SmartiAdapter { SystemLogger.info('Conversation found for channel'); // add message to conversation SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, requestBodyMessage); - // request analysis results - analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`); } else { SystemLogger.info('Conversation not found for channel'); const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); @@ -99,30 +97,37 @@ export class SmartiAdapter { SystemLogger.debug('Creating conversation:', JSON.stringify(requestBodyConversation, null, '\t')); // create conversation, send message along and request analysis - analysisResult = SmartiProxy.propagateToSmarti(verbs.post, 'conversation?analysis=true', requestBodyConversation); - analysisResult = analysisResult ? analysisResult.analysis : null; + let conversation = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', requestBodyConversation); + if(conversation && conversation.id){ + conversationId = conversation.id; + this.updateMapping(message, conversationId); + } } + // request analysis results + let analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`); SystemLogger.debug('analysisResult:', JSON.stringify(analysisResult, null, '\t')); if (analysisResult && analysisResult.conversation) { // update/insert channel/conversation specific timestamp - RocketChat.models.LivechatExternalMessage.update( - { - _id: message.rid - }, { - rid: message.rid, - knowledgeProvider: 'smarti', - conversationId: analysisResult.conversation, - ts: message.ts - }, { - upsert: true - } - ); - RocketChat.Notifications.notifyRoom(message.rid, 'newConversationResult', analysisResult); } } + static updateMapping(message, conversationId){ + RocketChat.models.LivechatExternalMessage.update( + { + _id: message.rid + }, { + rid: message.rid, + knowledgeProvider: 'smarti', + conversationId: conversationId, + ts: message.ts + }, { + upsert: true + } + ); + } + /** * Event implementation that publishes the conversation in Smarti. * From 8824313e37ef2fb7e5a44bb3fe2e45c77337096f Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Wed, 14 Feb 2018 12:07:11 +0100 Subject: [PATCH 17/26] Some refactoring in the conversation<->channel speedup --- .../hooks/sendMessageToKnowledgeAdapter.js | 9 ++- .../assistify-ai/server/lib/SmartiAdapter.js | 59 ++++++++++--------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js index 58a15bca71ca..d960e12e3195 100755 --- a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -28,13 +28,12 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { if (!knowledgeAdapter) { return; } - - SystemLogger.debug(`Send message ${message._id} to knowledgeAdapter (Meteor.defer()`); - + + SystemLogger.debug(`Send message ${message._id} to knowledgeAdapter (Meteor.defer()`); Meteor.defer(() => { try { - SystemLogger.debug(`Calling onMessage(${message._id});`); - knowledgeAdapter.onMessage(message); + SystemLogger.debug(`Calling onMessage(${message._id});`); + knowledgeAdapter.onMessage(message); } catch (e) { SystemLogger.error('Error using knowledge provider ->', e); } diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index 67c6104d2194..2f726e4860c1 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -33,6 +33,22 @@ export class SmartiAdapter { * @returns {*} */ static onMessage(message) { + function updateMapping(message, conversationId) { + // update/insert channel/conversation specific timestamp + RocketChat.models.LivechatExternalMessage.update( + { + _id: message.rid + }, { + rid: message.rid, + knowledgeProvider: 'smarti', + conversationId: conversationId, + ts: message.ts + }, { + upsert: true + } + ); + } + //TODO trigger on message update, if needed const requestBodyMessage = { @@ -58,17 +74,17 @@ export class SmartiAdapter { SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ message.rid }`); if (conversation && conversation.id) { - conversationId = conversation.id; - this.updateMapping(message, conversationId); + conversationId = conversation.id; + updateMapping(message, conversationId); } } if (conversationId) { - SystemLogger.info('Conversation found for channel'); + SystemLogger.debug(`Conversation ${conversationId} found for channel ${message.rid}`); // add message to conversation SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, requestBodyMessage); } else { - SystemLogger.info('Conversation not found for channel'); + SystemLogger.debug('Conversation not found for channel'); const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(message.rid); const supportArea = helpRequest ? helpRequest.supportArea : undefined; const room = RocketChat.models.Rooms.findOneById(message.rid); @@ -97,37 +113,22 @@ export class SmartiAdapter { SystemLogger.debug('Creating conversation:', JSON.stringify(requestBodyConversation, null, '\t')); // create conversation, send message along and request analysis - let conversation = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', requestBodyConversation); - if(conversation && conversation.id){ - conversationId = conversation.id; - this.updateMapping(message, conversationId); - } + + const conversation = SmartiProxy.propagateToSmarti(verbs.post, 'conversation', requestBodyConversation); + if (conversation && conversation.id) { + conversationId = conversation.id; + updateMapping(message, conversationId); + } } - // request analysis results - let analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`); - SystemLogger.debug('analysisResult:', JSON.stringify(analysisResult, null, '\t')); - if (analysisResult && analysisResult.conversation) { - // update/insert channel/conversation specific timestamp + // request analysis results + const analysisResult = SmartiProxy.propagateToSmarti(verbs.get, `conversation/${ conversationId }/analysis`); + SystemLogger.debug('analysisResult:', JSON.stringify(analysisResult, null, '\t')); + if (analysisResult) { RocketChat.Notifications.notifyRoom(message.rid, 'newConversationResult', analysisResult); } } - static updateMapping(message, conversationId){ - RocketChat.models.LivechatExternalMessage.update( - { - _id: message.rid - }, { - rid: message.rid, - knowledgeProvider: 'smarti', - conversationId: conversationId, - ts: message.ts - }, { - upsert: true - } - ); - } - /** * Event implementation that publishes the conversation in Smarti. * From 8eed7421bf1254089728907764da7a514f8020bf Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Wed, 14 Feb 2018 12:11:12 +0100 Subject: [PATCH 18/26] trying to get rid of the 404-error log when using the legacy service. --- packages/assistify-ai/server/SmartiProxy.js | 10 +++++++++- packages/assistify-ai/server/lib/SmartiAdapter.js | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/assistify-ai/server/SmartiProxy.js b/packages/assistify-ai/server/SmartiProxy.js index b3dfddd106ae..e6e2fc2ed870 100644 --- a/packages/assistify-ai/server/SmartiProxy.js +++ b/packages/assistify-ai/server/SmartiProxy.js @@ -29,10 +29,11 @@ export class SmartiProxy { * @param {String} method - the HTTP method to use * @param {String} path - the path to call * @param {Object} [body=null] - the payload to pass (optional) + * @param {Function} onError=null - custom error handler * * @returns {Object} */ - static propagateToSmarti(method, path, body = null) { + static propagateToSmarti(method, path, body = null, onError = null) { const url = `${ SmartiProxy.smartiUrl }${ path }`; const header = { 'X-Auth-Token': SmartiProxy.smartiAuthToken, @@ -47,6 +48,13 @@ export class SmartiProxy { SystemLogger.debug('Got unexpected result from Smarti', method, 'to', url, 'response', JSON.stringify(response)); } } catch (error) { + if (onError) { + const recoveryResult = onError(error); + if (recoveryResult !== undefined) { + return recoveryResult; + } + } + SystemLogger.error('Could not complete', method, 'to', url); SystemLogger.debug(error); } diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index 2f726e4860c1..da43a6add7b7 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -72,7 +72,14 @@ export class SmartiAdapter { conversationId = m.conversationId; } else { SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); - const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ message.rid }`); + const conversation = SmartiProxy.propagateToSmarti(verbs.get, + `legacy/rocket.chat?channel_id=${ message.rid }`, null, + function (error) { + // 404 is expected if no mapping exists + if (error.response.statusCode === 404) { + return null; + } + }); if (conversation && conversation.id) { conversationId = conversation.id; updateMapping(message, conversationId); From 8dbd9cd94d7204d2f87c987d7fa99842faf992d4 Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Wed, 14 Feb 2018 14:16:46 +0100 Subject: [PATCH 19/26] Fixed Smarti end-to-end tests --- tests/end-to-end/ui_smarti/01-integration.js | 30 ++++---------------- tests/pageobjects/assistify.page.js | 6 ++-- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index 34c27a1509d8..b13cc05e8091 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -1,11 +1,10 @@ /* eslint-env mocha */ -import mainContent from '../../pageobjects/main-content.page'; import sideNav from '../../pageobjects/side-nav.page'; import assistify from '../../pageobjects/assistify.page'; import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; -const topicName = 'smarti-test-topic4'; +const topicName = 'smarti-test-topic'; const topicExpert = 'rocketchat.internal.admin.test'; const shortTopicMessage = 'Das ist das neue Thema zu dem Anfragen erstellt werden und die Wissensbasis genutzt wird!'; const message = 'Mit allgemeinen Anfragen verschaffen Sie sich einen Überblick über den Markt, indem Sie Produkte, Preise und Bestellbedingungen unterschiedlicher Lieferanten und Dienstleister kennen lernen. In einem allgemeinen Anfragebrief bitten Sie zum die Zusendung von Katalogen, Prospekten, Preislisten und Produktmustern. Wie kann ich dieses Wissen nutzen?'; @@ -37,15 +36,6 @@ describe('[Smarti Integration]', () => { }); - describe.skip('Open Topic', () => { - it('switch to GENERAL', () => { - sideNav.openChannel('general'); - }); - it('switch back to Topic', () => { - sideNav.openChannel(topicName); - }); - }); - describe('Message', () => { it('it should send a message', () => { assistify.clickKnowledgebase(); @@ -74,18 +64,17 @@ describe('[Smarti Integration]', () => { it('create is successful', () => { assistify.createHelpRequest(topicName, message); + assistify.clickKnowledgebase(); }); it('knowledgebase answer visible', () => { - assistify.knowledgebasePickAnswer.waitForVisible(5000); - assistify.knowledgebasePickAnswer.click(); + assistify.clickKnowledgebase(); + assistify.knowledgebaseContent.waitForVisible(5000); }); it('post knowledgebase answer', () => { - assistify.knowledgebasePostBtn.waitForVisible(5000); - assistify.knowledgebasePostBtn.click(); - assistify.clickKnowledgebase(); - assistify.closeRequest(comment); + assistify.knowledgebasePickAnswer.waitForVisible(5000); + assistify.knowledgebasePickAnswer.click(); }); it('close request', () => { assistify.clickKnowledgebase(); @@ -100,11 +89,4 @@ describe('[Smarti Integration]', () => { assistify.closeTopic(topicName); }); }); - - // describe.skip('[BREAK]', () => { - // it('BREAK', () => { - // true.should.equal(false); - // }); - // }); - }); diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index 401184f8009b..d5ecd1dcb199 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -41,11 +41,11 @@ class Assistify extends Page { } get knowledgebasePickAnswer() { - return browser.element('.convMessage .middle .selectMessage'); + return browser.element('#widgetBody > div.widgetContent > div > div > div > div > div.postAction'); } - get knowledgebasePostBtn() { - return browser.element('.button.button-block'); + get knowledgebaseContent() { + return browser.element('[data-link="class{merge: messagesCnt toggle=\'parent\'}"]'); } // new Topic From 38dd8aca3aacde2d7aa5b1e68cf1817449a4412b Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Wed, 14 Feb 2018 17:48:46 +0100 Subject: [PATCH 20/26] Added test for keyword usage --- tests/end-to-end/ui_smarti/00-preparation.js | 36 +++++---- tests/end-to-end/ui_smarti/01-integration.js | 4 - tests/end-to-end/ui_smarti/02-widget.js | 77 +++++++++++++++++++ .../{02-cleanup.js => 05-cleanup.js} | 0 tests/pageobjects/assistify.page.js | 31 ++++---- 5 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 tests/end-to-end/ui_smarti/02-widget.js rename tests/end-to-end/ui_smarti/{02-cleanup.js => 05-cleanup.js} (100%) diff --git a/tests/end-to-end/ui_smarti/00-preparation.js b/tests/end-to-end/ui_smarti/00-preparation.js index b05f23b7a4c5..780143cb1cdc 100644 --- a/tests/end-to-end/ui_smarti/00-preparation.js +++ b/tests/end-to-end/ui_smarti/00-preparation.js @@ -57,27 +57,31 @@ describe('[Smarti Connection]', () => { .end(done); }); - it('create new client', function(done) { + it('delete client if exists', function(done) { if (typeof clientid !== 'undefined') { console.log('client was alread there', clientid); - done(); - } else { - request.post('/client') - .send({ - defaultClient: true, - description: '', - name: 'testclient' - }) - .set('Accept', 'application/json') - .end(function(err, res) { - clientid = res.body.id; - expect(res.status).to.be.equal(201); - console.log('clientid', res.body.id); - done(); - }); + request.del(`/client/${ clientid }`) + .expect(204) + .end(done); } }); + it('create new client', function(done) { + request.post('/client') + .send({ + defaultClient: true, + description: '', + name: 'testclient' + }) + .set('Accept', 'application/json') + .end(function(err, res) { + clientid = res.body.id; + expect(res.status).to.be.equal(201); + console.log('clientid', res.body.id); + done(); + }); + }); + it('check if right client was picked', function(done) { request.get('/client') .expect(200) diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index b13cc05e8091..0ceb093a75c5 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -11,10 +11,6 @@ const message = 'Mit allgemeinen Anfragen verschaffen Sie sich einen Überblick const answer = 'Das ist die Antwort auf diese Anfrage!'; const comment = 'Anfrage wurde erfolgreich beantwortet'; -import supertest from 'supertest'; - -export const request = supertest.agent('http://localhost:3000'); - import { checkIfUserIsAdmin } from '../../data/checks'; diff --git a/tests/end-to-end/ui_smarti/02-widget.js b/tests/end-to-end/ui_smarti/02-widget.js new file mode 100644 index 000000000000..e8aaebfea0e9 --- /dev/null +++ b/tests/end-to-end/ui_smarti/02-widget.js @@ -0,0 +1,77 @@ +import sideNav from '../../pageobjects/side-nav.page'; +import assistify from '../../pageobjects/assistify.page'; +import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; +import { checkIfUserIsAdmin } from '../../data/checks'; + +const topicName = 'widget-test-topic'; +const question = 'Wie komme ich von Frankfurt nach Berlin?'; +const question2 = 'Dortmund soll auch gefunden werden!'; +const tag = 'Frankfurt'; +const topicExpert = adminUsername; + +describe('[Smarti Widget]', () => { + + before(() => { + browser.pause(5000); // wait some time to make sure that all settings on both sides are actually persisted + + checkIfUserIsAdmin(adminUsername, adminEmail, adminPassword); // is broken -- if not admin it will log in as user or create a user + }); + + describe('[Topic]', () => { + it('Create new Topic for Testing', function() { + try { + sideNav.openChannel(topicName); + } catch (e) { + assistify.createTopic(topicName, topicExpert); + } + }); + }); + + describe('[Request]', () => { + + describe('First request', () => { + + it('create is successful', () => { + assistify.createHelpRequest(topicName, question); + }); + + it('close request', () => { + assistify.clickKnowledgebase(); + assistify.closeRequest('Danke!'); + + }); + }); + describe('Second request', () => { + + it('create is successful', () => { + assistify.createHelpRequest(topicName, question2); + assistify.clickKnowledgebase(); + }); + + it('knowledgebase answer is not visible', () => { + assistify.clickKnowledgebase(); + assistify.knowledgebaseContent.isVisible().should.equal(false); + }); + + it('Add missing tag', () => { + assistify.addNewKeyword(tag); + }); + + it('knowledgebase answer is visible now', () => { + assistify.knowledgebaseContent.isVisible().should.equal(true); + }); + + it('close request', () => { + assistify.clickKnowledgebase(); + assistify.closeRequest('Danke!'); + }); + }); + }); + + describe('Cleanup', () => { + it('close new Topic', () => { + console.log('TopicName for cleanup', topicName); + assistify.closeTopic(topicName); + }); + }); +}); diff --git a/tests/end-to-end/ui_smarti/02-cleanup.js b/tests/end-to-end/ui_smarti/05-cleanup.js similarity index 100% rename from tests/end-to-end/ui_smarti/02-cleanup.js rename to tests/end-to-end/ui_smarti/05-cleanup.js diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index d5ecd1dcb199..2a14d6352537 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -93,6 +93,14 @@ class Assistify extends Page { return browser.element('.flex-tab-container.border-component-color.opened .tab-button.active'); } + get addKeyword() { + return browser.element('#tags > ul > li.add'); + } + + get keywordTextBox() { + return browser.element('#newTagInput'); + } + get numberOfRequests() { return browser.element('#rocket-chat > aside > div.rooms-list > h3:nth-child(9) > span.badge'); } createTopic(topicName, expert) { @@ -111,7 +119,6 @@ class Assistify extends Page { this.topicExperts.setValue(expert); browser.pause(500); browser.keys(Keys.TAB); - // browser.element('.rc-popup-list__item').click(); browser.pause(500); browser.waitUntil(function() { @@ -159,24 +166,10 @@ class Assistify extends Page { this.sendMessageBtn.click(); } - // answerRequest(topicName, message) { - // sideNav.openChannel(topicName); - // - // this.sendTopicMessage(message); - // } - closeRequest(topicName, comment) { - // sideNav.openChannel(topicName); this.knowledgebaseTab.click(); - this.completeRequest.waitForVisible(5000); this.completeRequest.click(); - - // this.commentClose.waitForVisible(5000); - // this.commentClose.setValue(comment); - // - // this.commentCloseOK.waitForVisible(5000); - // this.commentCloseOK.click(); global.confirmPopup(); } @@ -196,6 +189,14 @@ class Assistify extends Page { this.knowledgebaseTab.click(); } + addNewKeyword(keyword) { + this.addKeyword.waitForVisible(5000); + this.addKeyword.click(); + this.keywordTextBox.setValue(keyword); + browser.keys(Keys.ENTER); + browser.pause(1000); + } + logoutRocketchat() { sideNav.accountMenu.waitForVisible(5000); sideNav.accountMenu.click(); From 3baf73b7c8f6c0a0faa8bc3e27f96d27c50c8254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Thu, 15 Feb 2018 09:28:09 +0100 Subject: [PATCH 21/26] Provide more information for outgoing integrations --- packages/rocketchat-integrations/server/lib/triggerHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rocketchat-integrations/server/lib/triggerHandler.js b/packages/rocketchat-integrations/server/lib/triggerHandler.js index f3457c3b1edc..0984351744bf 100644 --- a/packages/rocketchat-integrations/server/lib/triggerHandler.js +++ b/packages/rocketchat-integrations/server/lib/triggerHandler.js @@ -384,6 +384,8 @@ RocketChat.integrations.triggerHandler = new class RocketChatIntegrationHandler data.user_id = message.u._id; data.user_name = message.u.username; data.text = message.msg; + data.room = room; + data.siteUrl = RocketChat.settings.get('Site_Url'); if (message.alias) { data.alias = message.alias; From 0e51ace7f742f0d4388ca6df0b20ced54d625bda Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Thu, 15 Feb 2018 16:39:27 +0100 Subject: [PATCH 22/26] fix all linting errors plus 2 bug fixes in tests --- .scripts/seperateTesting.sh | 6 +-- .../hooks/sendMessageToKnowledgeAdapter.js | 4 +- .../assistify-ai/server/lib/AiApiAdapter.js | 2 + .../assistify-ai/server/lib/SmartiAdapter.js | 6 +-- .../AssistifyCreateExpertise.js | 1 + .../creationDialog/AssistifyCreateRequest.js | 1 + .../client/views/sideNav/listRequestsFlex.js | 3 +- .../server/methods/requestsList.js | 1 + .../server/models/LivechatExternalMessage.js | 1 + .../server/models/Rooms.js | 1 + .../server/models/Users.js | 1 + packages/meteor-accounts-saml/saml_utils.js | 1 + .../server/custom_oauth_server.js | 1 + .../client/views/SimpleRoomCreateDialog.js | 1 + .../server/functions/saveUser.js | 1 + .../server/functions/settings.js | 1 + .../server/lib/sendNotificationsOnMessage.js | 1 + .../rocketchat-lib/server/models/Rooms.js | 1 + .../server/slackbridge.js | 1 + server/publications/room.js | 1 + .../api/06-outgoing-integrations.js | 2 + ...om-roomtypes.js => 11-custom-roomtypes.js} | 48 ++++++++--------- tests/end-to-end/ui/14-global-announcement.js | 3 +- .../ui/15-settings-based-permissions.js | 19 ++----- tests/end-to-end/ui_smarti/00-preparation.js | 54 ++++++++----------- tests/end-to-end/ui_smarti/01-integration.js | 5 +- tests/end-to-end/ui_smarti/02-widget.js | 9 ++-- tests/end-to-end/ui_smarti/05-cleanup.js | 8 +-- tests/pageobjects/assistify.page.js | 2 +- 29 files changed, 89 insertions(+), 97 deletions(-) rename tests/end-to-end/api/{08-custom-roomtypes.js => 11-custom-roomtypes.js} (89%) diff --git a/.scripts/seperateTesting.sh b/.scripts/seperateTesting.sh index bbdfd3da873a..add2f1b2dd06 100755 --- a/.scripts/seperateTesting.sh +++ b/.scripts/seperateTesting.sh @@ -2,11 +2,11 @@ tmpPath=tests/end-to-end/temporary_staged_test rm -rf $tmpPath mkdir -p $tmpPath -[ -z "$num" ] && num=1 -for file in tests/end-to-end/ui/*.js; do +[ -z "$retry_test" ] && retry_test=1 +for file in tests/end-to-end/api/*.js; do failed=1 - for i in `seq 1 $num`; do + for i in `seq 1 $retry_test`; do echo '-------------- '$i' try ---------------' set -x cp $file $tmpPath diff --git a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js index d960e12e3195..cf73ecfd9db7 100755 --- a/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js +++ b/packages/assistify-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -29,10 +29,10 @@ RocketChat.callbacks.add('afterSaveMessage', function(message, room) { return; } - SystemLogger.debug(`Send message ${message._id} to knowledgeAdapter (Meteor.defer()`); + SystemLogger.debug(`Send message ${ message._id } to knowledgeAdapter (Meteor.defer()`); Meteor.defer(() => { try { - SystemLogger.debug(`Calling onMessage(${message._id});`); + SystemLogger.debug(`Calling onMessage(${ message._id });`); knowledgeAdapter.onMessage(message); } catch (e) { SystemLogger.error('Error using knowledge provider ->', e); diff --git a/packages/assistify-ai/server/lib/AiApiAdapter.js b/packages/assistify-ai/server/lib/AiApiAdapter.js index 02f0f677e141..6b891f8c4f84 100644 --- a/packages/assistify-ai/server/lib/AiApiAdapter.js +++ b/packages/assistify-ai/server/lib/AiApiAdapter.js @@ -1,3 +1,5 @@ +/* globals _ */ + export class ApiAiAdapter { constructor(adapterProps) { this.properties = adapterProps; diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index da43a6add7b7..9a186f080091 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -41,7 +41,7 @@ export class SmartiAdapter { }, { rid: message.rid, knowledgeProvider: 'smarti', - conversationId: conversationId, + conversationId, ts: message.ts }, { upsert: true @@ -74,7 +74,7 @@ export class SmartiAdapter { SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); const conversation = SmartiProxy.propagateToSmarti(verbs.get, `legacy/rocket.chat?channel_id=${ message.rid }`, null, - function (error) { + function(error) { // 404 is expected if no mapping exists if (error.response.statusCode === 404) { return null; @@ -87,7 +87,7 @@ export class SmartiAdapter { } if (conversationId) { - SystemLogger.debug(`Conversation ${conversationId} found for channel ${message.rid}`); + SystemLogger.debug(`Conversation ${ conversationId } found for channel ${ message.rid }`); // add message to conversation SmartiProxy.propagateToSmarti(verbs.post, `conversation/${ conversationId }/message`, requestBodyMessage); } else { diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateExpertise.js b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateExpertise.js index 0f4a3f0ab054..da79672cc1b7 100755 --- a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateExpertise.js +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateExpertise.js @@ -1,3 +1,4 @@ +/* globals _ */ /* globals AutoComplete, Deps */ import {RocketChat} from 'meteor/rocketchat:lib'; import {FlowRouter} from 'meteor/kadira:flow-router'; diff --git a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js index b8fecd429794..64787bfa1582 100755 --- a/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js +++ b/packages/assistify-help-request/client/views/creationDialog/AssistifyCreateRequest.js @@ -1,4 +1,5 @@ /* globals TAPi18n, AutoComplete */ +/* globals _ */ import {RocketChat} from 'meteor/rocketchat:lib'; import {FlowRouter} from 'meteor/kadira:flow-router'; import {ReactiveVar} from 'meteor/reactive-var'; diff --git a/packages/assistify-help-request/client/views/sideNav/listRequestsFlex.js b/packages/assistify-help-request/client/views/sideNav/listRequestsFlex.js index 26fcd530ba77..7cdf79c04425 100644 --- a/packages/assistify-help-request/client/views/sideNav/listRequestsFlex.js +++ b/packages/assistify-help-request/client/views/sideNav/listRequestsFlex.js @@ -1,4 +1,5 @@ - +/* globals _ */ +/* globals s */ function __guard__(value, transform) { return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; } diff --git a/packages/assistify-help-request/server/methods/requestsList.js b/packages/assistify-help-request/server/methods/requestsList.js index 9e398f4a313d..cd703c9a5c55 100644 --- a/packages/assistify-help-request/server/methods/requestsList.js +++ b/packages/assistify-help-request/server/methods/requestsList.js @@ -1,3 +1,4 @@ +/* globals _ */ Meteor.methods({ //adapted copy of server/methods/channelsList.js requestsList(filter, channelType, limit, sort) { diff --git a/packages/assistify-help-request/server/models/LivechatExternalMessage.js b/packages/assistify-help-request/server/models/LivechatExternalMessage.js index 977ba5190a82..31b9f97c4f4d 100755 --- a/packages/assistify-help-request/server/models/LivechatExternalMessage.js +++ b/packages/assistify-help-request/server/models/LivechatExternalMessage.js @@ -1,3 +1,4 @@ +/* globals _ */ /** * Created by OliverJaegle on 01.08.2016. */ diff --git a/packages/assistify-help-request/server/models/Rooms.js b/packages/assistify-help-request/server/models/Rooms.js index a2e026b897af..9d0a8385af96 100755 --- a/packages/assistify-help-request/server/models/Rooms.js +++ b/packages/assistify-help-request/server/models/Rooms.js @@ -1,3 +1,4 @@ +/* globals _ */ /** * Created by OliverJaegle on 01.08.2016. */ diff --git a/packages/assistify-help-request/server/models/Users.js b/packages/assistify-help-request/server/models/Users.js index aeb9558aa6ae..d6b462c8c727 100755 --- a/packages/assistify-help-request/server/models/Users.js +++ b/packages/assistify-help-request/server/models/Users.js @@ -1,3 +1,4 @@ +/* globals s */ /** * Created by OliverJaegle on 01.08.2016. * Expose features of the users-collection which are not exposed by default diff --git a/packages/meteor-accounts-saml/saml_utils.js b/packages/meteor-accounts-saml/saml_utils.js index 092122623d46..a8e5c0700b6e 100644 --- a/packages/meteor-accounts-saml/saml_utils.js +++ b/packages/meteor-accounts-saml/saml_utils.js @@ -1,4 +1,5 @@ /* globals SAML:true */ +/*eslint complexity: ["error", 40]*/ import zlib from 'zlib'; import xml2js from 'xml2js'; diff --git a/packages/rocketchat-custom-oauth/server/custom_oauth_server.js b/packages/rocketchat-custom-oauth/server/custom_oauth_server.js index f78b9e57edb8..a984bbbef1e4 100644 --- a/packages/rocketchat-custom-oauth/server/custom_oauth_server.js +++ b/packages/rocketchat-custom-oauth/server/custom_oauth_server.js @@ -1,4 +1,5 @@ /*globals OAuth*/ +/*eslint complexity: ["error", 40]*/ import _ from 'underscore'; const logger = new Logger('CustomOAuth'); diff --git a/packages/rocketchat-lib/client/views/SimpleRoomCreateDialog.js b/packages/rocketchat-lib/client/views/SimpleRoomCreateDialog.js index 2d43f6d1e21f..75eaa9830fac 100755 --- a/packages/rocketchat-lib/client/views/SimpleRoomCreateDialog.js +++ b/packages/rocketchat-lib/client/views/SimpleRoomCreateDialog.js @@ -1,4 +1,5 @@ /* globals TAPi18n, AutoComplete */ +/* globals _ */ import {RocketChat} from 'meteor/rocketchat:lib'; import {FlowRouter} from 'meteor/kadira:flow-router'; import {ReactiveVar} from 'meteor/reactive-var'; diff --git a/packages/rocketchat-lib/server/functions/saveUser.js b/packages/rocketchat-lib/server/functions/saveUser.js index ed727b385fa5..2b3ee3c2f2d8 100644 --- a/packages/rocketchat-lib/server/functions/saveUser.js +++ b/packages/rocketchat-lib/server/functions/saveUser.js @@ -1,4 +1,5 @@ /* globals Gravatar */ +/*eslint complexity: ["error", 50]*/ import _ from 'underscore'; import s from 'underscore.string'; diff --git a/packages/rocketchat-lib/server/functions/settings.js b/packages/rocketchat-lib/server/functions/settings.js index cef8a2d960e6..3223484a3150 100644 --- a/packages/rocketchat-lib/server/functions/settings.js +++ b/packages/rocketchat-lib/server/functions/settings.js @@ -1,3 +1,4 @@ +/*eslint complexity: ["error", 40]*/ import _ from 'underscore'; const blockedSettings = {}; diff --git a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js index dab8e6de7fa0..523892dad7e1 100644 --- a/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js +++ b/packages/rocketchat-lib/server/lib/sendNotificationsOnMessage.js @@ -1,4 +1,5 @@ /* globals Push */ +/*eslint complexity: ["error", 40]*/ import _ from 'underscore'; import s from 'underscore.string'; import moment from 'moment'; diff --git a/packages/rocketchat-lib/server/models/Rooms.js b/packages/rocketchat-lib/server/models/Rooms.js index 476cbe49d9d2..c3bd95493105 100644 --- a/packages/rocketchat-lib/server/models/Rooms.js +++ b/packages/rocketchat-lib/server/models/Rooms.js @@ -1,3 +1,4 @@ +/* globals RocketChat, SystemLogger */ import _ from 'underscore'; import s from 'underscore.string'; diff --git a/packages/rocketchat-slackbridge/server/slackbridge.js b/packages/rocketchat-slackbridge/server/slackbridge.js index 0c0974d998b5..ea113d406eae 100644 --- a/packages/rocketchat-slackbridge/server/slackbridge.js +++ b/packages/rocketchat-slackbridge/server/slackbridge.js @@ -1,4 +1,5 @@ /* globals logger */ +/*eslint complexity: ["error", 50]*/ import _ from 'underscore'; import util from 'util'; import url from 'url'; diff --git a/server/publications/room.js b/server/publications/room.js index 91a4d7b66525..d73c92f32003 100644 --- a/server/publications/room.js +++ b/server/publications/room.js @@ -1,3 +1,4 @@ +/* globals RocketChat, SystemLogger */ import _ from 'underscore'; const fields = { diff --git a/tests/end-to-end/api/06-outgoing-integrations.js b/tests/end-to-end/api/06-outgoing-integrations.js index 2ff1fdfb0862..6131bd947dc7 100644 --- a/tests/end-to-end/api/06-outgoing-integrations.js +++ b/tests/end-to-end/api/06-outgoing-integrations.js @@ -9,6 +9,8 @@ import supertest from 'supertest'; describe('Outgoing Integrations', function() { this.retries(0); + before(done => getCredentials(done)); + it('/integrations.create', (done) => { request.post(api('integrations.create')) .set(credentials) diff --git a/tests/end-to-end/api/08-custom-roomtypes.js b/tests/end-to-end/api/11-custom-roomtypes.js similarity index 89% rename from tests/end-to-end/api/08-custom-roomtypes.js rename to tests/end-to-end/api/11-custom-roomtypes.js index 155746f1ad71..30d88fe53c84 100644 --- a/tests/end-to-end/api/08-custom-roomtypes.js +++ b/tests/end-to-end/api/11-custom-roomtypes.js @@ -1,3 +1,4 @@ +/* eslint-env mocha */ import supertest from 'supertest'; import {adminUsername, adminPassword} from '../../data/user.js'; export const rcrequest = supertest.agent('http://localhost:3000'); @@ -21,7 +22,7 @@ describe('[Custom Room Types]', function() { .end(function(err, res) { authToken = res.body.data.authToken; userId = res.body.data.userId; - expect(res.status).to.be.equal(200); + res.status.should.be.equal(200); process.env.METEOR_TOKEN = authToken; done(); }); @@ -55,10 +56,9 @@ describe('[Custom Room Types]', function() { retry: 5 }, - function(error, userInfo) { + function(error) { if (error) { - console.log(`ERROR: ${ error }`); - expect(0).to.equal(1); + throw new Error(`ERROR: ${ error }`); } else { // console.log(userInfo); ddpClient.call( @@ -113,10 +113,9 @@ describe('[Custom Room Types]', function() { retry: 5 }, - function(error, userInfo) { + function(error) { if (error) { - console.log(`ERROR: ${ error }`); - expect(0).to.equal(1); + throw new Error(`ERROR: ${ error }`); } else { // console.log(userInfo); ddpClient.call( @@ -175,10 +174,9 @@ describe('[Custom Room Types]', function() { retry: 5 }, - function(error, userInfo) { + function(error) { if (error) { - console.log(`ERROR: ${ error }`); - expect(0).to.equal(1); + throw new Error(`ERROR: ${ error }`); } else { // console.log(userInfo); ddpClient.call( @@ -188,16 +186,16 @@ describe('[Custom Room Types]', function() { // console.log('GetRooms, result: ' + result); for (const i in result) { // console.log(result[i]); - if (result[i].t == 'test' && result[i].name == 'NewTestRoom') { + if (result[i].t === 'test' && result[i].name === 'NewTestRoom') { console.log('found test room'); - expect(result[i]._id).to.be.equal(roomid); + result[i]._id.should.be.equal(roomid); } } if (typeof err !== 'undefined') { console.log('error happend: '); console.log(err); } - expect(roomid).to.not.equal(undefined); + roomid.should.not.equal(undefined); }, function() { // callback which fires when server has finished // console.log('GetRooms - Function was finished'); // sending any updated documents as a result of @@ -238,10 +236,9 @@ describe('[Custom Room Types]', function() { retry: 5 }, - function(error, userInfo) { + function(error) { if (error) { - console.log(`ERROR: ${ error }`); - expect(0).to.equal(1); + throw new Error(`ERROR: ${ error }`); } else { // console.log(userInfo); ddpClient.call( @@ -250,7 +247,7 @@ describe('[Custom Room Types]', function() { function(err, result) { // callback which returns the method call results // console.log('EraseRoom, result: '); if (typeof result !== 'undefined') { - expect(result).to.be.equal(1); + result.should.be.equal(1); // console.log(result); } if (typeof err !== 'undefined') { @@ -297,10 +294,9 @@ describe('[Custom Room Types]', function() { retry: 5 }, - function(error, userInfo) { + function(error) { if (error) { - console.log(`ERROR: ${ error }`); - expect(0).to.equal(1); + throw new Error(`ERROR: ${ error }`); } else { // console.log(userInfo); ddpClient.call( @@ -310,18 +306,20 @@ describe('[Custom Room Types]', function() { // console.log('GetRooms, result: ' + result); for (const i in result) { // console.log(result[i]); - if (result[i].t == 'test' && result[i].name == 'NewTestRoom') { + if (result[i].t === 'test' && result[i].name === 'NewTestRoom') { console.log('found test room'); - expect(result[i]._id).to.be.equal(roomid); + result[i]._id.should.be.equal(roomid); + } else if (result[i].t !== 'd') { + result[i].t.should.not.equal('test'); + result[i].name.should.not.equal('NewTestRoom'); } - expect(result[i].t).to.not.equal('test'); - expect(result[i].name).to.not.equal('NewTestRoom'); + } if (typeof err !== 'undefined') { console.log('error happend: '); console.log(err); } - expect(roomid).to.not.equal(undefined); + roomid.should.not.equal(undefined); }, function() { // callback which fires when server has finished // console.log('GetRooms - Function was finished'); // sending any updated documents as a result of diff --git a/tests/end-to-end/ui/14-global-announcement.js b/tests/end-to-end/ui/14-global-announcement.js index a60b01ced157..521e128b3dad 100644 --- a/tests/end-to-end/ui/14-global-announcement.js +++ b/tests/end-to-end/ui/14-global-announcement.js @@ -1,3 +1,4 @@ +/* eslint-env mocha */ import supertest from 'supertest'; import {adminUsername, adminEmail, adminPassword} from '../../data/user.js'; import loginPage from '../../pageobjects/login.page'; @@ -22,7 +23,7 @@ describe('[Rocket.Chat Global Announcement Tests]', function() { .end(function(err, res) { authToken = res.body.data.authToken; userId = res.body.data.userId; - expect(res.status).to.be.equal(200); + res.status.should.be.equal(200); done(); }); }); diff --git a/tests/end-to-end/ui/15-settings-based-permissions.js b/tests/end-to-end/ui/15-settings-based-permissions.js index 1efe620f7587..f951f2d91d00 100644 --- a/tests/end-to-end/ui/15-settings-based-permissions.js +++ b/tests/end-to-end/ui/15-settings-based-permissions.js @@ -1,3 +1,4 @@ +/* eslint-env mocha */ import { adminUsername, adminEmail, adminPassword, username, email, password } from '../../data/user.js'; import admin from '../../pageobjects/administration.page'; import { checkIfUserIsValid, checkIfUserIsAdmin } from '../../data/checks'; @@ -30,7 +31,7 @@ describe('[Rocket.Chat Settings based permissions]', function() { manageSettingsPerm.click(); console.log('Value was unchecked', manageSettingsPerm.isSelected()); } - expect(manageSettingsPerm.isSelected()).to.equal(true); + manageSettingsPerm.isSelected().should.equal(true); done(); }); @@ -42,7 +43,7 @@ describe('[Rocket.Chat Settings based permissions]', function() { layoutTitelPerm.click(); console.log('Value was unchecked, now:', layoutTitelPerm.isSelected()); } - expect(layoutTitelPerm.isSelected()).to.equal(true); + layoutTitelPerm.isSelected().should.equal(true); done(); }); @@ -72,16 +73,6 @@ describe('[Rocket.Chat Settings based permissions]', function() { browser.pause(2000); console.log('New Titel value:', layoutTitelSetting.getValue()); saveSettings.click(); - - // layoutTitelSetting.setValue(''); - // browser.pause(2000); - // console.log('New Titel value:', layoutTitelSetting.getValue() ); - // saveSettings.click(); - // - // layoutTitelSetting.setValue(newTitle); - // browser.pause(2000); - // console.log('New Titel value:', layoutTitelSetting.getValue() ); - // saveSettings.click(); done(); }); }); @@ -111,12 +102,12 @@ describe('[Rocket.Chat Settings based permissions]', function() { admin.rolesPermissionGrid.waitForVisible(5000); manageSettingsPerm.click(); - expect(manageSettingsPerm.isSelected()).to.equal(false); + manageSettingsPerm.isSelected().should.equal(false); expandSBP.click(); layoutTitelPerm.click(); - expect(layoutTitelPerm.isSelected()).to.equal(false); + layoutTitelPerm.isSelected().should.equal(false); done(); }); diff --git a/tests/end-to-end/ui_smarti/00-preparation.js b/tests/end-to-end/ui_smarti/00-preparation.js index 780143cb1cdc..90e92bcef684 100644 --- a/tests/end-to-end/ui_smarti/00-preparation.js +++ b/tests/end-to-end/ui_smarti/00-preparation.js @@ -1,5 +1,4 @@ /* eslint-env mocha */ - import supertest from 'supertest'; import { adminUsername, adminPassword } from '../../data/user.js'; @@ -20,7 +19,7 @@ describe('[Smarti Connection]', () => { .expect(200) .expect('Content-Type', 'application/vnd.spring-boot.actuator.v1+json;charset=UTF-8') .expect((res) => { - expect(res.body).to.have.property('status', 'UP'); + res.body.should.have.property('status', 'UP'); }) .end(done); }); @@ -48,7 +47,7 @@ describe('[Smarti Connection]', () => { .expect(200) .expect(function(res) { for (const cl in res.body) { - if (res.body[cl].name == 'testclient') { + if (res.body[cl].name === 'testclient') { clientid = res.body[cl].id; console.log('check if client exists', clientid); } @@ -63,6 +62,8 @@ describe('[Smarti Connection]', () => { request.del(`/client/${ clientid }`) .expect(204) .end(done); + } else { + done(); } }); @@ -76,7 +77,7 @@ describe('[Smarti Connection]', () => { .set('Accept', 'application/json') .end(function(err, res) { clientid = res.body.id; - expect(res.status).to.be.equal(201); + res.status.should.be.equal(201); console.log('clientid', res.body.id); done(); }); @@ -87,11 +88,11 @@ describe('[Smarti Connection]', () => { .expect(200) .expect(function(res) { for (const cl in res.body) { - if (res.body[cl].name == 'testclient') { - expect(res.body[cl].id, clientid); + if (res.body[cl].name === 'testclient') { + res.body[cl].id.should.be.equal(clientid); } } - expect(clientid).to.not.equal(undefined); + clientid.should.not.equal(undefined); }) .end(done); }); @@ -116,7 +117,7 @@ describe('[Smarti Connection]', () => { .send({}) .end(function(err, res) { token = res.body.token; - expect(res.status).to.be.equal(201); + res.status.should.be.equal(201); console.log('token', res.body.token); done(); }); @@ -132,7 +133,7 @@ describe('[Smarti Connection]', () => { .end(function(err, res) { authToken = res.body.data.authToken; userId = res.body.data.userId; - expect(res.status).to.be.equal(200); + res.status.should.be.equal(200); console.log('authToken', authToken); console.log('userId', userId); done(); @@ -243,7 +244,7 @@ describe('[Smarti Connection]', () => { .end(function(err, res) { authToken = res.body.data.authToken; userId = res.body.data.userId; - expect(res.status).to.be.equal(200); + res.status.should.be.equal(200); console.log('authToken', authToken); console.log('userId', userId); done(); @@ -256,8 +257,8 @@ describe('[Smarti Connection]', () => { .set('X-User-Id', userId) .expect(200) .end(function(err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.value).to.be.equal('testclient'); + res.status.should.be.equal(200); + res.body.value.should.be.equal('testclient'); console.log('', res.body.value); done(); }); @@ -269,8 +270,8 @@ describe('[Smarti Connection]', () => { .set('X-User-Id', userId) .expect(200) .end(function(err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.value).to.be.equal('0'); + res.status.should.be.equal(200); + res.body.value.should.be.equal('0'); console.log('Assistify_AI_Source', res.body.value); done(); }); @@ -282,8 +283,8 @@ describe('[Smarti Connection]', () => { .set('X-User-Id', userId) .expect(200) .end(function(err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.value).to.be.equal(true); + res.status.should.be.equal(200); + res.body.value.should.be.equal(true); console.log('Assistify_AI_Enabled', res.body.value); done(); }); @@ -295,8 +296,8 @@ describe('[Smarti Connection]', () => { .set('X-User-Id', userId) .expect(200) .end(function(err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.value).to.be.equal('key123'); + res.status.should.be.equal(200); + res.body.value.should.be.equal('key123'); console.log('Assistify_AI_RocketChat_Webhook_Token', res.body.value); done(); }); @@ -308,8 +309,8 @@ describe('[Smarti Connection]', () => { .set('X-User-Id', userId) .expect(200) .end(function(err, res) { - expect(res.status).to.be.equal(200); - expect(res.body.value).to.be.equal('http://localhost:8080/'); + res.status.should.be.equal(200); + res.body.value.should.be.equal('http://localhost:8080/'); console.log('Assistify_AI_Smarti_Base_URL', res.body.value); done(); }); @@ -325,17 +326,4 @@ describe('[Smarti Connection]', () => { .end(done); }); }); - - // describe.skip('[BREAK]', ()=> { - // it('BREAK', ()=> { - // true.should.equal(false); - // }); - // }); - }); - -// OVERWRITE_SETTING_Assistify_AI_Smarti_Domain: testclient -// - OVERWRITE_SETTING_Assistify_AI_Source: 0 -// - OVERWRITE_SETTING_Assistify_AI_Enabled: true -// - OVERWRITE_SETTING_Assistify_AI_RocketChat_Webhook_Token: key123 -// - OVERWRITE_SETTING_Assistify_AI_Smarti_Base_URL: http://localhost:8080/ diff --git a/tests/end-to-end/ui_smarti/01-integration.js b/tests/end-to-end/ui_smarti/01-integration.js index 0ceb093a75c5..506893c5477e 100644 --- a/tests/end-to-end/ui_smarti/01-integration.js +++ b/tests/end-to-end/ui_smarti/01-integration.js @@ -9,7 +9,6 @@ const topicExpert = 'rocketchat.internal.admin.test'; const shortTopicMessage = 'Das ist das neue Thema zu dem Anfragen erstellt werden und die Wissensbasis genutzt wird!'; const message = 'Mit allgemeinen Anfragen verschaffen Sie sich einen Überblick über den Markt, indem Sie Produkte, Preise und Bestellbedingungen unterschiedlicher Lieferanten und Dienstleister kennen lernen. In einem allgemeinen Anfragebrief bitten Sie zum die Zusendung von Katalogen, Prospekten, Preislisten und Produktmustern. Wie kann ich dieses Wissen nutzen?'; const answer = 'Das ist die Antwort auf diese Anfrage!'; -const comment = 'Anfrage wurde erfolgreich beantwortet'; import { checkIfUserIsAdmin } from '../../data/checks'; @@ -52,7 +51,7 @@ describe('[Smarti Integration]', () => { }); it('close request', () => { assistify.clickKnowledgebase(); - assistify.closeRequest(comment); + assistify.closeRequest(); }); }); @@ -74,7 +73,7 @@ describe('[Smarti Integration]', () => { }); it('close request', () => { assistify.clickKnowledgebase(); - assistify.closeRequest(comment); + assistify.closeRequest(); }); }); }); diff --git a/tests/end-to-end/ui_smarti/02-widget.js b/tests/end-to-end/ui_smarti/02-widget.js index e8aaebfea0e9..73434769dbc2 100644 --- a/tests/end-to-end/ui_smarti/02-widget.js +++ b/tests/end-to-end/ui_smarti/02-widget.js @@ -1,3 +1,4 @@ +/* eslint-env mocha */ import sideNav from '../../pageobjects/side-nav.page'; import assistify from '../../pageobjects/assistify.page'; import { adminUsername, adminEmail, adminPassword } from '../../data/user.js'; @@ -33,11 +34,11 @@ describe('[Smarti Widget]', () => { it('create is successful', () => { assistify.createHelpRequest(topicName, question); - }); - + }); + it('close request', () => { assistify.clickKnowledgebase(); - assistify.closeRequest('Danke!'); + assistify.closeRequest(); }); }); @@ -63,7 +64,7 @@ describe('[Smarti Widget]', () => { it('close request', () => { assistify.clickKnowledgebase(); - assistify.closeRequest('Danke!'); + assistify.closeRequest(); }); }); }); diff --git a/tests/end-to-end/ui_smarti/05-cleanup.js b/tests/end-to-end/ui_smarti/05-cleanup.js index 417483a63eb5..625ea402bc81 100644 --- a/tests/end-to-end/ui_smarti/05-cleanup.js +++ b/tests/end-to-end/ui_smarti/05-cleanup.js @@ -2,7 +2,6 @@ import supertest from 'supertest'; export const request = supertest.agent('http://localhost:8080'); -import assistify from '../../pageobjects/assistify.page'; export const credentials = { username: 'admin', @@ -17,7 +16,7 @@ describe('[Smarti Cleanup]', () => { .expect(200) .expect(function(res) { clientid = res.body[0].id; - expect(clientid).to.not.equal(undefined); + clientid.should.not.equal(undefined); }) .end(done); }); @@ -27,9 +26,4 @@ describe('[Smarti Cleanup]', () => { .expect(204) .end(done); }); - - it('logout of rocket.chat', function(done) { - assistify.logoutRocketchat(); - done(); - }); }); diff --git a/tests/pageobjects/assistify.page.js b/tests/pageobjects/assistify.page.js index 2a14d6352537..f2a79239205e 100644 --- a/tests/pageobjects/assistify.page.js +++ b/tests/pageobjects/assistify.page.js @@ -166,7 +166,7 @@ class Assistify extends Page { this.sendMessageBtn.click(); } - closeRequest(topicName, comment) { + closeRequest() { this.knowledgebaseTab.click(); this.completeRequest.waitForVisible(5000); this.completeRequest.click(); From 5ba993212b6fffc83f6e5e1c60b59b9ad5d2a202 Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Thu, 15 Feb 2018 17:30:23 +0100 Subject: [PATCH 23/26] update smarti version to 0.7.0-BETA --- .scripts/smarti.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scripts/smarti.sh b/.scripts/smarti.sh index eeb5bb06641d..932f3928eb44 100755 --- a/.scripts/smarti.sh +++ b/.scripts/smarti.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -docker pull assisitfy/smarti:0.6.1 +docker pull assisitfy/smarti:0.7.0-BETA #docker run -d --net=host assisitfy/smarti:0.6.1 From f38532a36d1599cd7a36ad8f860b53ec17a27359 Mon Sep 17 00:00:00 2001 From: ThomasRoehl Date: Thu, 15 Feb 2018 19:07:32 +0100 Subject: [PATCH 24/26] fix dockerfile smarti version --- .scripts/dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scripts/dockerfile b/.scripts/dockerfile index 6ad62992009b..2427286a4a6c 100644 --- a/.scripts/dockerfile +++ b/.scripts/dockerfile @@ -1,4 +1,4 @@ -FROM assisitfy/smarti:0.6.1 +FROM assisitfy/smarti:0.7.0-BETA USER root ADD [\ "https://repo1.maven.org/maven2/edu/stanford/nlp/stanford-corenlp/3.8.0/stanford-corenlp-3.8.0.jar", \ From 4cc03fe11dccf336ff1ff58b9a4be50773b9769f Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Thu, 15 Feb 2018 19:25:39 +0100 Subject: [PATCH 25/26] found another place where the legacy/rocket.chat service is called (closes #236) --- packages/assistify-ai/server/SmartiProxy.js | 2 +- packages/assistify-ai/server/lib/SmartiAdapter.js | 3 +-- .../assistify-ai/server/methods/SmartiWidgetBackend.js | 7 ++++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/assistify-ai/server/SmartiProxy.js b/packages/assistify-ai/server/SmartiProxy.js index e6e2fc2ed870..5c7cea60e3fa 100644 --- a/packages/assistify-ai/server/SmartiProxy.js +++ b/packages/assistify-ai/server/SmartiProxy.js @@ -55,7 +55,7 @@ export class SmartiProxy { } } - SystemLogger.error('Could not complete', method, 'to', url); + SystemLogger.error('Could not complete', method, 'to', url, error.response); SystemLogger.debug(error); } } diff --git a/packages/assistify-ai/server/lib/SmartiAdapter.js b/packages/assistify-ai/server/lib/SmartiAdapter.js index da43a6add7b7..75751df81afe 100644 --- a/packages/assistify-ai/server/lib/SmartiAdapter.js +++ b/packages/assistify-ai/server/lib/SmartiAdapter.js @@ -73,8 +73,7 @@ export class SmartiAdapter { } else { SystemLogger.debug('Smarti - Trying legacy service to retrieve conversation ID...'); const conversation = SmartiProxy.propagateToSmarti(verbs.get, - `legacy/rocket.chat?channel_id=${ message.rid }`, null, - function (error) { + `legacy/rocket.chat?channel_id=${ message.rid }`, null, (error) => { // 404 is expected if no mapping exists if (error.response.statusCode === 404) { return null; diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index 34a786a07045..aa1a2fd3fc2b 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -32,7 +32,12 @@ Meteor.methods({ return !RocketChat.authz.hasPermission(userId, 'send-many-messages'); } } - )(verbs.get, `legacy/rocket.chat?channel_id=${ channelId }`); + )(verbs.get, `legacy/rocket.chat?channel_id=${ channelId }`, null, (error) => { + // 404 is expected if no mapping exists + if (error.response.statusCode === 404) { + return null; + } + }); if (conversation && conversation.id) { let timestamp = conversation.messages && From 79a096d91a476b316162f71888710bf51f967b7b Mon Sep 17 00:00:00 2001 From: Jakob Frank Date: Thu, 15 Feb 2018 19:28:10 +0100 Subject: [PATCH 26/26] fixed indentation (#236) --- .../server/methods/SmartiWidgetBackend.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js index aa1a2fd3fc2b..7b6b7127475a 100644 --- a/packages/assistify-ai/server/methods/SmartiWidgetBackend.js +++ b/packages/assistify-ai/server/methods/SmartiWidgetBackend.js @@ -7,7 +7,7 @@ const querystring = require('querystring'); /** @namespace RocketChat.RateLimiter.limitFunction */ /** - * The SmartiWidgetBackend handles all interactions triggered by the Smarti widget (not by Rocket.Chat hooks). + * The SmartiWidgetBackend handles all interactions triggered by the Smarti widget (not by Rocket.Chat hooks). * These 'Meteor.methods' are made available to be accessed via DDP, to be used in the Smarti widget. */ Meteor.methods({ @@ -33,11 +33,11 @@ Meteor.methods({ } } )(verbs.get, `legacy/rocket.chat?channel_id=${ channelId }`, null, (error) => { - // 404 is expected if no mapping exists - if (error.response.statusCode === 404) { - return null; - } - }); + // 404 is expected if no mapping exists + if (error.response.statusCode === 404) { + return null; + } + }); if (conversation && conversation.id) { let timestamp = conversation.messages &&