From 4a696e39f16bc70b2e71f0c2ea6a1b3543b1b856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Feb 2017 13:50:28 +0100 Subject: [PATCH 01/83] manual copy of ai and common re-use stuff from p2phelp --- .meteor/packages | 3 + packages/dbs-ai/README.md | 3 + .../dbs-ai/assets/icons/communication.png | Bin 0 -> 1104 bytes .../dbs-ai/assets/icons/peerToPeerHelp.png | Bin 0 -> 1599 bytes .../dbs-ai/assets/icons/sapTransaction.png | Bin 0 -> 1645 bytes .../dbs-ai/assets/stylesheets/redlink.less | 556 ++++++++++++++++++ packages/dbs-ai/client/redlink_ui.js | 12 + .../views/app/tabbar/externalSearch.html | 152 +++++ .../client/views/app/tabbar/externalSearch.js | 389 ++++++++++++ .../views/app/tabbar/redlinkInlineResult.html | 120 ++++ .../views/app/tabbar/redlinkInlineResult.js | 137 +++++ .../views/app/tabbar/redlinkQueries.html | 15 + .../client/views/app/tabbar/redlinkQueries.js | 22 + .../client/views/app/tabbar/redlinkQuery.html | 41 ++ .../client/views/app/tabbar/redlinkQuery.js | 100 ++++ .../app/tabbar/redlinkResultContainers.html | 16 + .../app/tabbar/redlinkResultContainers.js | 94 +++ packages/dbs-ai/dbs-ai.js | 5 + .../dbs-ai/i18n/externalSearch.de.i18n.yml | 33 ++ .../dbs-ai/i18n/externalSearch.en.i18n.yml | 33 ++ .../i18n/knowledgeIntegration.de.i18n.yml | 22 + .../i18n/knowledgeIntegration.en.i18n.yml | 22 + packages/dbs-ai/package.js | 49 ++ packages/dbs-ai/server/config.js | 35 ++ .../hooks/closeLivechatKnowledgeAdapter.js | 20 + .../hooks/sendMessageToKnowledgeAdapter.js | 42 ++ packages/dbs-ai/server/lib/AiApiAdapter.js | 33 ++ .../server/lib/KnowledgeAdapterProvider.js | 54 ++ packages/dbs-ai/server/lib/Redlink.js | 290 +++++++++ .../dbs-ai/server/methods/retrieveResults.js | 82 +++ .../methods/updateKnowledgeProviderResult.js | 14 + packages/dbs-common/README.md | 0 .../client/lib/globalTemplateHelpers.js | 21 + packages/dbs-common/dbs-common.js | 5 + packages/dbs-common/lib/core.js | 2 + packages/dbs-common/lib/duration.js | 23 + packages/dbs-common/lib/testing.js | 3 + packages/dbs-common/package.js | 23 + packages/dbs-common/server/config.js | 11 + .../dbs-common/server/customHttpsCerts.js | 44 ++ 40 files changed, 2526 insertions(+) create mode 100755 packages/dbs-ai/README.md create mode 100755 packages/dbs-ai/assets/icons/communication.png create mode 100755 packages/dbs-ai/assets/icons/peerToPeerHelp.png create mode 100755 packages/dbs-ai/assets/icons/sapTransaction.png create mode 100755 packages/dbs-ai/assets/stylesheets/redlink.less create mode 100755 packages/dbs-ai/client/redlink_ui.js create mode 100755 packages/dbs-ai/client/views/app/tabbar/externalSearch.html create mode 100755 packages/dbs-ai/client/views/app/tabbar/externalSearch.js create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkQuery.html create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html create mode 100755 packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.js create mode 100644 packages/dbs-ai/dbs-ai.js create mode 100755 packages/dbs-ai/i18n/externalSearch.de.i18n.yml create mode 100755 packages/dbs-ai/i18n/externalSearch.en.i18n.yml create mode 100755 packages/dbs-ai/i18n/knowledgeIntegration.de.i18n.yml create mode 100755 packages/dbs-ai/i18n/knowledgeIntegration.en.i18n.yml create mode 100755 packages/dbs-ai/package.js create mode 100755 packages/dbs-ai/server/config.js create mode 100755 packages/dbs-ai/server/hooks/closeLivechatKnowledgeAdapter.js create mode 100755 packages/dbs-ai/server/hooks/sendMessageToKnowledgeAdapter.js create mode 100755 packages/dbs-ai/server/lib/AiApiAdapter.js create mode 100755 packages/dbs-ai/server/lib/KnowledgeAdapterProvider.js create mode 100755 packages/dbs-ai/server/lib/Redlink.js create mode 100755 packages/dbs-ai/server/methods/retrieveResults.js create mode 100755 packages/dbs-ai/server/methods/updateKnowledgeProviderResult.js create mode 100644 packages/dbs-common/README.md create mode 100755 packages/dbs-common/client/lib/globalTemplateHelpers.js create mode 100644 packages/dbs-common/dbs-common.js create mode 100755 packages/dbs-common/lib/core.js create mode 100755 packages/dbs-common/lib/duration.js create mode 100755 packages/dbs-common/lib/testing.js create mode 100755 packages/dbs-common/package.js create mode 100755 packages/dbs-common/server/config.js create mode 100755 packages/dbs-common/server/customHttpsCerts.js diff --git a/.meteor/packages b/.meteor/packages index 88892cbd1466..a6860796c212 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -164,3 +164,6 @@ underscorestring:underscore.string yasaricli:slugify yasinuslu:blaze-meta deepwell:bootstrap-datepicker2 + +dbs:common +dbs:ai diff --git a/packages/dbs-ai/README.md b/packages/dbs-ai/README.md new file mode 100755 index 000000000000..549cf740ca69 --- /dev/null +++ b/packages/dbs-ai/README.md @@ -0,0 +1,3 @@ +This package contains all artifacts of the redlink-integration which can be isolated. +Some parts deeply integrate with existing Rocket.Chat-components. +For the sake of reduced dependency, potential standard-components shall implement a switch an load modified parts from this package if needed. diff --git a/packages/dbs-ai/assets/icons/communication.png b/packages/dbs-ai/assets/icons/communication.png new file mode 100755 index 0000000000000000000000000000000000000000..5e542f3c583df5d98574307336ba23164c3235d1 GIT binary patch literal 1104 zcmV-W1h4yvP)WFU8GbZ8()Nlj2>E@cM*00Xp1L_t(o!|j%DNZfTC z$6r6QpUE;|9u^+>;3Bb!tHJL<3Zf56R*c~`us!(U0}GZ{up4317&hdJ%{|z~P#VY{ zh=r4GwkBm}WEneXKUV%ixQ_Y_Z`~~p9o!C+`z3sOaIWd*-}7t_y7!YGe81n%=lA-4 zKi@xZ$SSL>vf5TDTL}(ZmMk#O{Cqz7`2dgMa+N3#4R9_tB@>@sf5wgXDsk}K!D3ehMt{XQs(j$F?R~O?n-aOSpRdHTM%TF@w7mu!st~ZOU^D(pGlj7&7Zylz*+fJsv$m2@NVbK5WUxKZ*B!$FVv#~ z#z$AMl3TkTWOhP&hZ2B`hFv=Wxb^cGOCKX^;>P9xbIY2Px2si14CC#%ls2Ty2INfZ zc`U%u_y$!)iXsak8T%};e4sf^SRVXG&RWTLy>^lVP8B;cL`Tc}3{1v01sM+X%CV!} zm^L`-+VSmA8Hrn8^e}2?@GUlaJ>namNu8bQRxY@oQ^L_%DJ>~_AU+nJlwZfc;*)W6 z(NL(A>nAy}H}yOw!+tsb?hrHg2UfS!@2*hNb~8qoEU1|YNS0#IDYdKuA zd^KVLpR{!wtT->%5&Ao8l(b4LaYN1=^I_i2th%UozeST)yrId(tr7Y7{2*5&t1P*y z;W>L0tx!o1FyPgtJE-E&>Nxb$UTP}V2b+yf$&K&7rhn8};tf||bMB+9<5kLbD9W~2 zet@}fK>Pz=G8O%U$c(-5K+QwHXMH1^F#{QoPKr79EyDqmNMa+P4jQ}8vcE)8{u96r z`NGe=;vEd$J1C9=d~o_8rFq*};&e1M(jyly8(0Y&wcNvn6WT-J$SSL>vdZfJpnm{k W-GlaK?FV!K0000GwTvZu|pZDC|aa*-gEfF^*N+3kUDt0UW0o0hv zKSP9I4b@PqnS17DaWxB}iK#JbthglN2DP($?@XJ;AqrWQNGak6zfdD!+6I%xN&yLN zS_%o>!p>~joxA7tLuWVZ-I=*}cHb{|a^Bzj{+_va&YW|uu)^?t|8fzz54av!4ZI3G z8OQO@_wL<$Y9+m8CB;DyT;rU35?IqDeHyqnilUd7+utm=&|16NIrmr#Fo27I@#Xf1 z6~Kwe&A^2zvTLoimoC?1*>I|QaV7&}E?TZfM{vIH-(aoX1>l@Jn4x42coD!_yE6!a zYdZ1EG$DN7ztR}^C@zTV#69U~(n?-UA! z0Ju9*-yn zl7T3Sev=u_TKj$A!6vC=#+WW1c29@-&)@}ug!21$8CnDbr!|>5mIBV@kf#-pD zB~niTcSKS2R)=5D#S7Y`;UgosGg1nlp#v$MCg1GCmv zt+i^c{YxuNxm-T#dEWKF>xt9_aU72%_5TOw`~HopdR;>EMt67jt>fe4@3b;)#591a zPPP+`j*kAy&{ zXbNYoU9GBLNfaEPoSb|(0~kQ90rz$maU91#YchOmQ#cX%0!x2uznhqtn9E|Os&6;0 z)hzlwd-l8}B8L*g>+|{iB}?E`^%KbkB61*$5r8qK+Neo=tcyJV*2MG5C2%aA-o$Zy zEUS@oZffy0W1MUP_kksFB9coAdwP1>&b-Ajf9GgSj4Yw5r<;sjF#mb+nmL^m&dkiL z%4+1Cn-Y7K`^$75SF(&!%-!^>T-)xNetg7Zz z^@V!9zJGjt{46xq*1^HSRn=;Bmx%bldmD8xipV`-81C!DENkTR`Sl|5M-jO?kvane zQ4~!`e}Dgb;y6A4T-B_Js@_{Fm41wmXWVdRjjbN!#H7Om}hq z-A3BM_c(y>`?q_ZccRr!xE0vY(2zNK^5j-y%=^PI{59}6@SS#ss``ZZzW-ej+0#aM z3iu@O7H|ysFtATVhQly?mZkf$h1kA*drvNxy9L-e-^25~OGZaW&suB$L}C}RC|(2l z1i)H51pJ7lr?KOz`n6K2^n9UExLZ{pNaRn5$Wy9%R8=QMq$VP3RP{n&1MpQ6X9iXM zeW_IX*1*6(ZgzHdm#Tgb=$TK8$bQfB`isTliG?#n5Ck80&TSKs%YbQBJ#g;axrZy2 zO07^R+^DLL1Kq9eQit)wD2j#w1_lP!%*@R6scO#iydz^{V+)&sOecP8?FQhNz~&V~ zIaPgBRR_!Ea_UOb(fhd|2)^W;`vGthOE;$NVhWfLkza*j_^+%*%X)d(SXDN29&!V) x4)`FjssVCVMBV_N1D>9qp8jj4QmJ+7^FMa)LDKI#Jh1=(002ovPDHLkV1n}I4vPQ) literal 0 HcmV?d00001 diff --git a/packages/dbs-ai/assets/icons/sapTransaction.png b/packages/dbs-ai/assets/icons/sapTransaction.png new file mode 100755 index 0000000000000000000000000000000000000000..24ca93e60b1d9414519c00220d967fe0777c3a32 GIT binary patch literal 1645 zcmV-z29o)SP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vG>`v3qf`vLriO6LFo02*{fSaefwW^{L9 za%BKeVQFr3E>1;MAa*k@H7+qQF!XYv000HtNklluG1b;C8WXiCh$*6gMN=&W1Z0sXZvhl(ga;Oa3lJ0$X{*JmfFP7=1quqX z3nJTN2N94>wy$#^7kH41OXDB+B){A{-^`r(-kF(8AONC9P!tn_qL>gA#e|?JCIm$> zAt;IoK~YQyief@g6ca?rU8ABRiILI+x-p8?k!e1peYpc-LhEuTC}{|5Po~I%yxsIj zH=zX_di;~lH-loEK<60jU9*pSa!@=Q!=rtnc~aN8FuiXA%^b6#m(UEIgjUeSUaSkd zA_+d|N`6uwBK@-sFoJJ#)?bLSP;i_rqPL7=$%zTpN-RwQNe({ z&t>l%Tw9;((x0Zs$RX8=J*Np&NhPLy6{yee(!zeZ} zvlJU4tXI*)#z6>_?avSp%)`hcJn$gX+mJ+WJS(&WI@KR?jEa z_tC{hwXR8H?=bS)2XVZ(8}Gz1qsOaY6j=+y(>0UmN3tSBw`0Sx-`$lG*GY47&LH`h zV#6gBZ%H^>*&>Fam7sufIA^x<>Ua!|jNK8bCQq!4T3YIp@086Crk{7ys{bgFYqL}BXyv{{~c!R45%tN?}7W7CoI zPRc`WB%fhUZO}BN3|#|L2|Ux;!FJ3AIp={6=3|Mn97X}moT3mPM?K(2O^<9BFpwW^7Ge{}N~gR^uDLSO=w#usO9sUC%qH?i}Q&kH_7{ zKDb^};oaa;kXtScqy@~t9h}N-<3|9|cRNu1XlmZ#qeWO1R?fGW!J!ct%GkUgvas@a z8Lt9kV`F$Ls0<{%k8c%c_6AE2%xX?;koIN4;*4-*ST@8LbYP+64)i_q@VI}N?*LZ( zT!eGX(kmXrDyD*O0p%@ypzRsha&oF-_t-Xn5>&*uW{<70DtzG*VHYfSX9k{a zj)P1+a$s^G2V|EC+ItJD4(G$`QVpuw`uUXO(K9erz1t7Fu`ci)9OA3gTc!^7^t@Qj z&fe5#N;8Vt=gNY8(!O*G+wr32ILO%fHcS|sLyNG$;TGt-G|+~t_&}D&r+)D$Z|X%K ze`AN3QS-23tO>}+N_jqX_U7Vi$6u_16`VQw3z_2}vPgmD@$s#xZ0dWI^R6FoVgFX zh+=FDnYtJ=W4UWK=GguNTdy$lAm5ia$3ez+H?Y>v~Yp*sEr}AG=hWuR?d*b!cx(f!5Y!{F&D}**`%+Z6B67WW&leWQ)?zofO=e zlL?!67PM?n1{Q2jg|0<17Vb#J+xs%{p5xzmlj#PQDVS$<9mY0kShRB-6Wbdw+ns@> z_UUSte4nnKH_^XnXDa4fr^9G_CU$v*Zc_TY(;=y^J~OA~D`$UWU(zI*?P3e^EM?U+ zVT&1Cr^1viHmPf%Ieun8_ik#N!G2IC>^(v@a^?<5j+kwBit;qyf9gkx)OS8-Qpm_` zkeMwr@`_ku!;IRbb?@;|u0Q7IN&bpMw9o6NaXV6s=V8&@eMrMPS*`(rCv50N! z*8L}4!;iR-lx?MgRZAjOEZpK0gA#RKR+67Ya05%d%M00000NkvXXu0mjfhQ$b< literal 0 HcmV?d00001 diff --git a/packages/dbs-ai/assets/stylesheets/redlink.less b/packages/dbs-ai/assets/stylesheets/redlink.less new file mode 100755 index 000000000000..17a45754ade5 --- /dev/null +++ b/packages/dbs-ai/assets/stylesheets/redlink.less @@ -0,0 +1,556 @@ +.suppressDatetimepicker { + .xdsoft_datetimepicker { + left: 5000px !important; + display: none !important; + position: absolute !important; + } +} + +.external-search-content { + .title { + .title-icon { + position: relative; + top: 1px; + margin-right: 10px; + img { + width: 20px; + } + } + } + + .icon-spinner:before { + -webkit-animation: spin 2s 20 linear; + -o-animation: spin 2s 20 linear; + animation: spin 2s 20 linear; + + } + + .external-message { + padding: 0; + position: relative; + padding-bottom: 20px; + &:after { + content: " "; + position: absolute; + border-bottom: 1px solid #666; + left: 10px; + right: 10px; + bottom: 0; + } + .knowledge-title, .queries-title { + background-color: #ccc; + font-weight: 700; + padding: 10px; + margin-bottom: 8px; + font-size: 15px; + } + .queries-title { + text-align: center; + color: #666; + font-size: 14px; + } + .query-template-wrapper { + position: relative; + margin: 0 15px 10px; + max-height: 2000px; + transition: max-height .25s ease-in-out; + &.collapsed { + max-height: 36px; + overflow: hidden; + .icon-up-open::before { + content: '\e855' + } + } + &.Rejected { + display: none; + } + &.Confirmed { + .query-template-tools-icon { + &.icon-ok { + color: #1b5ab8; + } + &.icon-cancel { + display: none; + } + } + } + + &.spinner { + .knowledge-queries-wrapper { + opacity: 0.5; + .queries-spinner { + display: block; + } + } + } + + .query-template-tools-wrapper { + position: absolute; + right: 5px; + top: 10px; + z-index: 10; + .query-template-tools-icon { + padding: 10px 5px; + cursor: pointer; + } + } + + .query-template { + background-color: #fff; + border: 1px solid #ccc; + color: #666; + margin-bottom: -1px; + + .value-line-wrapper > div.field-with-label:last-child { + float: right; + } + .value-line-wrapper { + padding: 0 10px 2px; + position: relative; + min-height: 40px; + .field-with-label { + display: inline-block; + &.editing { + position: absolute; + width: 100%; + left: 0; + top: 0; + z-index: 5; + background-color: #fff; + .knowledge-input-wrapper { + width: 78%; + padding: 0; + margin: 0; + .knowledge-base-value { + width: 80%; + background-color: #fff !important; + color: #444; + font-weight: bold; + border: 1px solid #1b5ab8; + } + } + .knowledge-base-label { + padding: 0 2%; + width: 18%; + } + .edit-icons-set { + cursor: pointer; + display: inline-block; + } + .knowledge-input-wrapper.active .knowledge-base-tooltip { + display: none; + } + } + } + } + + .edit-icons-set { + display: none; + } + + .icon-wrapper { + position: relative; + background-color: #efefef; + border-radius: 5px; + cursor: pointer; + .icon-spinner { + position: absolute; + left:6px; + background-color: #efefef; + display: none; + } + &.spinner { + cursor: default; + .icon-spinner { + display: inline-block!important; + } + } + } + + .value-seperator, .icon-wrapper { + min-width: 33px; + display: inline-block; + padding: 5px 6px; + margin: 0 3px; + } + + .knowledge-base-label { + font-weight: bold; + margin-right: 5px; + display: inline-block; + min-width: 55px; + } + .knowledge-input-wrapper { + position: relative; + display: inline-block; + .knowledge-base-tooltip { + display: none; + position: absolute; + height: auto; + width: 200px; + background-color: white; + left: -45px; + color: rgb(68, 68, 68); + margin-top: -8px; + z-index: 100; + box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1); + &::after { + bottom: 100%; + left: 40%; + content: ""; + position: absolute; + border: solid transparent; + border-bottom-color: white; + border-width: 10px; + margin-left: -10px; + } + .knowledge-context-menu-item { + padding: 6px; + cursor: pointer; + border-bottom: 1px solid #ccc; + &:hover:not(.disabled) { + background-color: #1b5ab8; + color: #fff; + } + &.disabled { + color: rgba(68, 68, 68, 0.4); + cursor: default; + } + } + } + .knowledge-base-value { + font-weight: 400; + background-color: #1b5ab8 !important; + color: #fff; + padding: 5px; + height: 30px; + border-radius: 5px; + margin-bottom: 8px; + display: inline-block; + min-width: 100px; + width: auto; + cursor: pointer; + position: relative; + &.empty-style { + background-color: #efefef !important; + color: #666; + border: 1px dotted #444; + .delete-item { + display: none; + } + } + } + &.active { + .knowledge-base-tooltip { + display: block; + } + } + } + } + } + + .knowledge-queries-wrapper { + padding-bottom: 10px; + position: relative; + .queries-spinner { + position: absolute; + left: 40%; + font-size: 60px; + color: #000; + z-index: 5; + top: 20px; + display: none; + } + .confidence { + font-size: 0.7rem; + display: inline-block; + margin-left: 5px; + } + .query-item-wrapper { + max-height: 2000px; + transition: max-height .25s ease-in-out; + margin: 0 0 -1px; + &.collapsed { + max-height: 39px; + overflow: hidden; + .icon-up-open::before { + content: '\e855' + } + .arrow-navigation-wrapper { + visibility: hidden; + } + } + .query-item { + padding: 5px; + border: 1px solid #CCC; + position: relative; + .query-servicename { + font-size: 12px; + max-width: 225px; + word-wrap: break-word; + display: inline-block; + overflow: hidden; + white-space: nowrap; + vertical-align: middle; + text-overflow: ellipsis; + } + .icon-wrapper { + position: relative; + display: inline-block; + top: 1px; + font-size: 15px; + } + .query-item-logo { + float: left; + line-height: 20px; + margin-top: 2px; + margin-left: 33px; + img { + width: 20px; + margin: 0 4px; + } + .creator_label { + position: relative; + top: -4px; + color: #666; + font-weight: bold; + font-size: 15px; + } + } + .query-item-url, .query-results-toggle { + display: inline-block; + padding: 4px; + color: #666; + float: right; + } + .query-item-url { + margin-right: 5px; + background-color: #e2e2e2; + &:hover { + color: #444; + } + } + .query-results-toggle { + .js-toggle-results-expanded { + cursor: pointer; + padding: 3px; + } + position: absolute; + } + } + + .results-slider > div:last-child { + border-bottom: 0; + } + .results-dirty { + min-height: 40px; + padding: 40px 15px; + text-align: center; + background-color: #fff; + .icon-wrapper { + font-size: 40px; + } + } + + .query-preview { + margin-bottom: 0px; + border: 1px solid #ccc; + border-top: none; + background-color: #fff; + position: relative; + em { + font-weight: bold; + } + .travel-bahn { + border: 1px solid #ccc; + margin: 0 8px -1px 0; + width: 274px; + border-left: none; + float: left; + &:last-child { + border-left: 1px solid #ccc; + border-right: none; + margin-right: 0px; + float: right; + } + + .from-to-wrapper { + background-color: #ccc; + padding: 10px 15px; + max-width: 100%; + min-height: 90px; + .from-wrapper { + float: left; + } + .to-wrapper { + float: right; + text-align: right; + } + .location { + font-size: 12px; + font-weight: bold; + margin-bottom: 10px; + max-width: 136px; + } + .time { + font-size: 25px; + font-weight: bold; + margin-bottom: 10px; + } + .day-offset { + font-size: 12px; + font-weight: normal; + } + } + .details-wrapper { + background-color: #fff; + padding: 10px 15px; + min-height: 100px; + .details { + font-size: 12px; + font-weight: bold; + margin-bottom: 5px; + } + } + .price-wrapper { + min-height: 60px; + padding: 10px 15px; + .price-low { + float: left; + } + .price-standard { + float: right; + text-align: right; + } + .price-label { + font-size: 12px; + font-weight: bold; + font-style: italic; + margin-bottom: 5px; + } + .price { + font-size: 22px; + font-weight: bold; + } + } + .action-wrapper { + min-height: 40px; + padding: 10px 15px; + color: #fff; + a { + color: #fff; + } + .action-icon { + display: inline-block; + margin: 0 10px 0 0; + font-size: 15px; + float: right; + } + .link-external, .link-message { + padding: 10px; + cursor: pointer; + width: 110px; + } + .link-external { + float: left; + background-color: #ff0000; + } + .link-message { + float: right; + text-align: right; + background-color: #097E28; + } + } + .not-available { + min-height: 40px; + padding: 10px 15px; + color: #fff; + .action-icon { + display: inline-block; + margin: 0 10px 0 0; + font-size: 15px; + float: right; + color: #ff0000; + } + .js-not-available { + background-color: #ccc; + padding: 10px; + cursor: not-allowed; + } + } + } + + .query-preview-navigation { + text-align: center; + min-width: 50px; + position: relative; + padding-top: 3px; + padding-bottom: 3px; + border-top: 2px solid #ccc; + .js-previous-result, .js-next-result { + cursor: pointer; + padding: 3px; + } + } + + .query-preview-headline-wrapper { + display: none; + border-bottom: 1px solid #ccc; + .query-preview-headline { + float: left; + padding: 10px 15px; + } + } + .result-item-wrapper { + max-height: 2000px; + transition: max-height .25s ease-in-out; + border-bottom: 1px solid #ccc; + &.collapsed { + max-height: 35px; + overflow: hidden; + .icon-up-open::before { + content: '\e855' + } + } + .icon-up-open { + padding: 10px 5px; + cursor: pointer; + } + } + .query-preview-result-title { + padding: 10px 15px; + font-style: italic; + .result-title-wrapper { + float: left; + width: 85%; + } + .icon-wrapper { + float: right; + width: 12%; + text-align: right; + .icon-link-ext { + font-size: 15px; + color: #444; + } + } + } + .query-preview-result-body { + padding: 10px 15px; + .conversationMessages { + .conversationMessage { + text-align: left; + padding-bottom: 10px; + .provider{ + padding-left: 2em; + } + .seeker{ + font-style: italic; + } + } + } + } + } + } + } + } +} diff --git a/packages/dbs-ai/client/redlink_ui.js b/packages/dbs-ai/client/redlink_ui.js new file mode 100755 index 000000000000..2fec9bb4a43b --- /dev/null +++ b/packages/dbs-ai/client/redlink_ui.js @@ -0,0 +1,12 @@ +RocketChat.TabBar.removeButton('external-search'); + +RocketChat.TabBar.addButton({ + groups: ['livechat', 'channel'], + id: 'external-search', + i18nTitle: 'Knowledge_Base', + icon: 'icon-lightbulb', + template: 'reisebuddy_externalSearch', + order: 0, + initialOpen: true +}); + diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.html b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html new file mode 100755 index 000000000000..ade0bb5f8175 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js new file mode 100755 index 000000000000..1b815f7cc7d0 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js @@ -0,0 +1,389 @@ +for (var tpl in Template) { + if (Template.hasOwnProperty(tpl) && tpl.startsWith('dynamic_redlink_')) { + Template[tpl].onRendered(function () { + this.$('.field-with-label').each(function(indx, wrapperItem) { + const inputField = $(wrapperItem).find(".knowledge-base-value"); + $(wrapperItem).find(".icon-cancel").data("initValue", inputField.val()); + }); + this.$('.datetime-field').each(function(indx, inputFieldItem) { + $.datetimepicker.setDateFormatter({ + parseDate: function (date, format) { + var d = moment(date, format); + return d.isValid() ? d.toDate() : false; + }, + formatDate: function (date, format) { + return moment(date).format(format); + } + }); + $(inputFieldItem).datetimepicker({ + dayOfWeekStart: 1, + format: 'L LT', + formatTime: 'LT', + formatDate: 'L', + validateOnBlur:false // prevent validation to use questionmark as placeholder + }); + }); + }); + } +} + +Template.reisebuddy_externalSearch.helpers({ + messages() { + return RocketChat.models.LivechatExternalMessage.findByRoomId(this.rid, {ts: 1}); + }, + dynamicTemplateExists() { + return !!Template['dynamic_redlink_' + this.queryType]; + }, + queryTemplate() { + return 'dynamic_redlink_' + this.queryType; + }, + filledQueryTemplate() { + var knowledgebaseSuggestions = RocketChat.models.LivechatExternalMessage.findByRoomId(Template.currentData().rid, + {ts: -1}).fetch(), filledTemplate = []; + if (knowledgebaseSuggestions.length > 0) { + const tokens = knowledgebaseSuggestions[0].result.tokens; + $(knowledgebaseSuggestions[0].result.queryTemplates).each(function (indexTpl, queryTpl) { + let extendedQueryTpl = queryTpl, filledQuerySlots = []; + + /* tokens und queryTemplates mergen */ + $(queryTpl.querySlots).each(function (indxSlot, slot) { + if (slot.tokenIndex != -1) { + const currentToken = tokens[slot.tokenIndex]; + if (currentToken.type === "Date" && typeof currentToken.value === "object") { + slot.clientValue = moment(currentToken.value.date).format("L LT"); + } else { + slot.clientValue = currentToken.value; + } + slot.tokenVal = currentToken; + } else { + slot.clientValue = "?"; + } + filledQuerySlots.push(slot); + }); + + extendedQueryTpl.filledQuerySlots = filledQuerySlots; + extendedQueryTpl.forItem = function (itm) { + let returnValue = { + htmlId: Meteor.uuid(), + item: "?", + itemStyle: "empty-style", + inquiryStyle: "disabled", + label: 'topic_' + itm, + parentTplIndex: indexTpl //todo replace with looping index in html + }; + if (typeof extendedQueryTpl.filledQuerySlots === "object") { + const slot = extendedQueryTpl.filledQuerySlots.find((ele) => ele.role === itm); + if (slot) { + returnValue = _.extend(slot, returnValue); + returnValue.item = slot.clientValue; + if (!_.isEmpty(slot.inquiryMessage)) { + returnValue.inquiryStyle = ''; + } + if (returnValue.item !== "" && returnValue.item !== "?") { + returnValue.itemStyle = ""; + } + if (returnValue.tokenType === "Date") { + returnValue.itemStyle = returnValue.itemStyle + " datetime-field"; + } + } + } + return returnValue; + }; + + extendedQueryTpl.dummyEinstiegshilfe = function() { //todo: Entfernen, wenn Redlink die Hilfeart erkennt + return { + htmlId: Meteor.uuid(), + item: "Einstiegshilfe", + itemStyle: "", + inquiryStyle: "", + label: t('topic_supportType'), + parentTplIndex: 0 //todo replace with looping index in html + }; + }; + filledTemplate.push(extendedQueryTpl); + }); + } + return filledTemplate; + }, + queriesContext(queries, templateIndex){ + const instance = Template.instance(); + $(queries).each(function (indx, queryItem) { + if(queries[indx].creator && typeof queries[indx].creator == "string") { + queries[indx].replacedCreator = queries[indx].creator.replace(/\./g, "_"); + } else { + queries[indx].replacedCreator = ""; + } + }); + return { + queries: queries, + roomId: instance.data.rid, + templateIndex: templateIndex + } + } + , + helpRequestByRoom(){ + const instance = Template.instance(); + return instance.helpRequest.get(); + } +}); + + +Template.reisebuddy_externalSearch.events({ + /** + * Notifies that a query was confirmed by an agent (aka. clicked) + */ + 'click .knowledge-queries-wrapper .query-item a ': function (event, instance) { + const query = $(event.target).closest('.query-item'); + let externalMsg = instance.externalMessages.get(); + externalMsg.result.queryTemplates[query.data('templateIndex')].queries[query.data('queryIndex')].state = 'Confirmed'; + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(),(err) => { + if (err) {//TODO logging error + } + }); + }, + /** + * Hide datetimepicker when right mouse clicked + */ + 'mousedown .field-with-label': function(event, instance) { + if(event.button === 2) { + $("body").addClass("suppressDatetimepicker"); + setTimeout(() => { + $('.datetime-field').datetimepicker("hide"); + $("body").removeClass("suppressDatetimepicker"); + }, 500); + } + }, + /* + * open contextmenu with "-edit, -delete and -nachfragen" + * */ + 'contextmenu .field-with-label': function (event, instance) { + event.preventDefault(); + instance.$(".knowledge-input-wrapper.active").removeClass("active"); + instance.$(event.currentTarget).find(".knowledge-input-wrapper").addClass("active"); + $(document).off("mousedown.contextmenu").on("mousedown.contextmenu", function (e) { + if (!$(e.target).parent(".knowledge-base-tooltip").length > 0) { + $(".knowledge-input-wrapper.active").removeClass("active"); + } + }); + }, + 'click .query-template-tools-wrapper .icon-up-open': function (event) { + $(event.currentTarget).closest(".query-template-wrapper").toggleClass("collapsed"); + }, + /** + * Mark a template as confirmed + */ + 'click .query-template-tools-wrapper .icon-ok': function (event, instance) { + const query = $(event.target).closest('.query-template-wrapper'); + let externalMsg = instance.externalMessages.get(); + externalMsg.result.queryTemplates[query.data('templateIndex')].state = 'Confirmed'; + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(), (err) => { + if (err) {//TODO logging error + } + }); + }, + /** + * Mark a template as rejected. + */ + 'click .query-template-tools-wrapper .icon-cancel': function (event, instance) { + const query = $(event.target).closest('.query-template-wrapper'); + let externalMsg = instance.externalMessages.get(); + externalMsg.result.queryTemplates[query.data('templateIndex')].state = 'Rejected'; + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(), (err) => { + if (err) {//TODO logging error + } + }); + }, + + 'keydup .knowledge-base-value, keydown .knowledge-base-value': function (event, inst) { + const inputWrapper = $(event.currentTarget).closest(".field-with-label"), + ENTER_KEY = 13, + ESC_KEY = 27, + TAB_KEY = 9, + keycode = event.keyCode; + if (inputWrapper.hasClass("editing")) { + switch (keycode) { + case ENTER_KEY: + inputWrapper.find(".icon-floppy").click(); + break; + case ESC_KEY: + case TAB_KEY: + inputWrapper.find(".icon-cancel").click(); + break; + } + } else if(keycode != TAB_KEY) { + $(".field-with-label.editing").removeClass("editing"); + inputWrapper.addClass('editing'); + } + }, + 'click .knowledge-input-wrapper .icon-cancel': function (event, instance) { + const inputWrapper = $(event.currentTarget).closest(".field-with-label"), + inputField = inputWrapper.find(".knowledge-base-value"); + inputWrapper.removeClass("editing"); + inputField.val($(event.currentTarget).data("initValue")); + }, + 'click .knowledge-input-wrapper .icon-floppy': function (event, instance) { + event.preventDefault(); + const inputWrapper = $(event.currentTarget).closest(".field-with-label"), + templateWrapper = $(event.currentTarget).closest(".query-template-wrapper"), + inputField = inputWrapper.find(".knowledge-base-value"); + inputWrapper.removeClass("editing"); + templateWrapper.addClass("spinner"); + const saveValue = inputField.val(); + inputWrapper.find(".icon-cancel").data("initValue", saveValue); + + let externalMsg = instance.externalMessages.get(); + const newToken = { + confidence: 0.95, + messageIdx: -1, + start: -1, + end: -1, + state: "Confirmed", + hints: [], + type: _.isEmpty(inputWrapper.data('tokenType')) ? 'Unknown' : inputWrapper.data('tokenType'), + origin: "Agent", + value: inputField.hasClass('datetime-field') ? + { + grain: 'minute', + date: moment(saveValue, "L LT").toISOString() + } : + saveValue + }; + + externalMsg.result.tokens.push(newToken); + externalMsg.result.queryTemplates[inputWrapper.data('parentTplIndex')].querySlots = _.map(externalMsg.result.queryTemplates[inputWrapper.data('parentTplIndex')].querySlots, + (query) => { + if (query.role === inputWrapper.data('slotRole')) { + query.tokenIndex = externalMsg.result.tokens.length - 1; + } + return query; + }); + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(), (err) => { + templateWrapper.removeClass("spinner"); + instance.$(".knowledge-input-wrapper.active").removeClass("active"); + if (err) {//TODO logging error + } + }); + }, + 'click .knowledge-base-tooltip .edit-item, click .knowledge-base-value, click .knowledge-base-label': function (event, instance) { + event.preventDefault(); + const inputWrapper = $(event.currentTarget).closest(".field-with-label"), + inputField = inputWrapper.find(".knowledge-base-value"); + + if (!inputWrapper.hasClass('editing')) { + $(".field-with-label.editing").removeClass("editing"); + inputField.focus().select(); + inputWrapper.addClass('editing'); + } + }, + /** + * Deletes a token from a queryTemplate and mark it as rejected. + */ + 'click .knowledge-base-tooltip .delete-item': function (event, instance) { + event.preventDefault(); + const field = $(event.target).closest('.field-with-label'), + templateIndex = field.attr('data-parent-tpl-index'), + slotRole = field.attr('data-slot-role'); + let externalMsg = instance.externalMessages.get(); + externalMsg.result.queryTemplates[templateIndex].querySlots = _.map(externalMsg.result.queryTemplates[templateIndex].querySlots, + (query) => { + if (query.role === slotRole) { + query.tokenIndex = -1; + } + return query; + }); + externalMsg.result.tokens[field.attr('data-token-index')].state = "Rejected"; + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(), (err) => { + instance.$(".knowledge-input-wrapper.active").removeClass("active"); + if (err) {//TODO logging error + } + }); + + }, + /** + * Writes the inqury of an queryTemplateSlot to the chatWindowInputField. + */ + 'click .knowledge-base-tooltip .chat-item:not(.disabled)': function (event, inst) { + event.preventDefault(); + const rlData = _.first(RocketChat.models.LivechatExternalMessage.findByRoomId(inst.roomId, {ts: -1}).fetch()); + if (rlData && rlData.result) { + const input = inst.$(event.target).closest('.field-with-label'), + slotRole = input.attr('data-slot-role'); + const qSlot = _.find(rlData.result.queryTemplates[input.attr('data-parent-tpl-index')].querySlots, (slot) => { + return slot.role == slotRole; + }); + if (qSlot && qSlot.inquiryMessage) { + const inputBox = $('#chat-window-' + inst.roomId + ' .input-message'); + const initialInputBoxValue = inputBox.val() ? inputBox.val() + ' ' : ''; + inputBox.val(initialInputBoxValue + qSlot.inquiryMessage).focus().trigger('keyup'); + inst.$(".knowledge-input-wrapper.active").removeClass("active"); + } + } + }, + /** + * Switches the tokens between two slots within a query template. + */ + 'click .external-message .icon-wrapper .icon-exchange': function(event, instance) { + const changeBtn = $(event.target).parent().closest('.icon-wrapper'), + left = changeBtn.prevAll('.field-with-label'), + right = changeBtn.nextAll('.field-with-label'), + leftTokenIndex = parseInt(left.attr('data-token-index')), + rightTokenIndex = parseInt(right.attr('data-token-index')); + if(changeBtn.hasClass("spinner")) { + return; + } + changeBtn.addClass("spinner"); + let externalMsg = instance.externalMessages.get(); + externalMsg.result.queryTemplates[left.data('parentTplIndex')].querySlots = _.map(externalMsg.result.queryTemplates[left.data('parentTplIndex')].querySlots, + (query) => { + if (query.tokenIndex === leftTokenIndex) { + query.tokenIndex = rightTokenIndex; + } else if (query.tokenIndex === rightTokenIndex) { + query.tokenIndex = leftTokenIndex; + } + return query; + }); + instance.externalMessages.set(externalMsg); + Meteor.call('updateKnowledgeProviderResult', instance.externalMessages.get(),(err) => { + changeBtn.removeClass("spinner"); + if (err) {//TODO logging error + } + }); + } +}); + +Template.reisebuddy_externalSearch.onCreated(function () { + this.externalMessages = new ReactiveVar([]); + this.helpRequest = new ReactiveVar({}); + this.roomId = null; + + const self = this; + this.autorun(() => { + self.roomId = Template.currentData().rid; + self.subscribe('livechat:externalMessages', self.roomId); + const extMsg = RocketChat.models.LivechatExternalMessage.findByRoomId(self.roomId, {ts: -1}).fetch(); + if (extMsg.length > 0) { + self.externalMessages.set(extMsg[0]); + } + + if(self.roomId){ + self.subscribe('p2phelp:helpRequest', self.roomId); + const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(self.roomId); + self.helpRequest.set(helpRequest); + + if(!helpRequest){ //todo remove after PoC: Non-reactive method call + Meteor.call('p2phelp:helpRequestByRoomId', self.roomId,(err, result) => { + if(!err){ + self.helpRequest.set(result); + } else { + console.log(err); + } + }); + } + } + }); +}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html new file mode 100755 index 000000000000..4d5bb44ac071 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js new file mode 100755 index 000000000000..b80c8aa4224d --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js @@ -0,0 +1,137 @@ +Template.redlinkInlineResult._copyReplySuggestion = function (event, instance) { + if (instance.data.result.replySuggestion) { + $('#chat-window-' + instance.data.roomId + ' .input-message').val(instance.data.result.replySuggestion); + } +}; + +Template.redlinkInlineResult.helpers({ + templateName(){ + const instance = Template.instance(); + + let templateSuffix = "generic"; + switch (instance.data.result.creator) { + case 'bahn.de': + templateSuffix = "bahn_de"; + break; + case 'community.bahn.de': + templateSuffix = "VKL_community"; + break; + case 'VKL': + templateSuffix = "VKL_community"; + break; + default: + if (!!Template['redlinkInlineResult_' + instance.data.result.creator]) { + templateSuffix = instance.data.result.creator; + } else { + templateSuffix = "generic"; + } + break; + } + return 'redlinkInlineResult_' + templateSuffix; + }, + templateData(){ + const instance = Template.instance(); + return { + result: instance.data.result, + roomId: instance.data.roomId + } + } +}); + +Template.redlinkInlineResult.events({ + 'click .js-copy-reply-suggestion': function (event, instance) { + return Template.redlinkInlineResult._copyReplySuggestion(event, instance) + } +}); + +//----------------------------------- Generic helper as fallback ------------------------------ + +Template.redlinkInlineResult_generic.helpers({ + relevantKeyValues(){ + const instance = Template.instance(); + + let keyValuePairs = []; + for (key in instance.data.result) { + keyValuePairs.push({key: key, value: instance.data.result[key]}); + } + + return keyValuePairs; + } +}); + +//------------------------------------- Bahn.de ----------------------------------------------- + +Template.redlinkInlineResult_bahn_de.events({ + 'click .js-copy-reply-suggestion': function (event, instance) { + return Template.redlinkInlineResult._copyReplySuggestion(event, instance) + } +}); + +Template.redlinkInlineResult_bahn_de.helpers({ + durationformat(val){ + return new _dbs.Duration(val * 60 * 1000).toHHMMSS(); + } +}); + +//----------------------------------- VKL and community --------------------------------------- +Template.redlinkInlineResult_VKL_community.helpers({ + classExpanded(){ + const instance = Template.instance(); + return instance.state.get('expanded') ? 'expanded' : 'collapsed'; + } +}); + +Template.redlinkInlineResult_VKL_community.events({ + 'click .result-item-wrapper .js-toggle-result-preview-expanded': function (event, instance) { + const current = instance.state.get('expanded'); + instance.state.set('expanded', !current); + }, +}); + +Template.redlinkInlineResult_VKL_community.onCreated(function () { + const instance = this; + + this.state = new ReactiveDict(); + this.state.setDefault({ + expanded: false + }); +}); + +//-------------------------------------- Peer-to-Peer-Helpdesk -------------------------------- +Template.redlinkInlineResult_conversation.helpers({ + classExpanded(){ + const instance = Template.instance(); + return instance.state.get('expanded') ? 'expanded' : 'collapsed'; + }, + originQuestion(){ + const instance = Template.instance(); + return instance.data.result.messages[0].text; + }, + latestResponse(){ + const instance = Template.instance(); + return instance.data.result.messages.filter((message)=>message.origin === 'provider').pop().text; + }, + + subsequentCommunication(){ + const instance = Template.instance(); + return instance.data.result.messages.slice(1); + } +}); + +Template.redlinkInlineResult_conversation.events({ + 'click .result-item-wrapper .js-toggle-result-preview-expanded': function (event, instance) { + const current = instance.state.get('expanded'); + instance.state.set('expanded', !current); + } +}); + +Template.redlinkInlineResult_conversation.onCreated(function () { + const instance = this; + + let transformedSnippet = instance.data.result.snippet; + + this.state = new ReactiveDict(); + this.state.setDefault({ + expanded: false + }); +}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html new file mode 100755 index 000000000000..07f554ed620f --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html @@ -0,0 +1,15 @@ + diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js new file mode 100755 index 000000000000..815543ec2a71 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js @@ -0,0 +1,22 @@ +Template.redlinkQueries.helpers({ + queryContext(query, queryIndex){ + const instance = Template.instance(); + function hasInlineSupport(item) { + return item.inlineResultSupport===true; + } + const queriesWithInlineSupport = instance.data.queries + .filter(hasInlineSupport); + + return { + query: query, + maxConfidence: Math.max(...queriesWithInlineSupport.map((query) => query.confidence)), + roomId: instance.data.roomId, + templateIndex: instance.data.templateIndex, + queryIndex: queryIndex + } + } +}); + +Template.redlinkQueries.events({ + +}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.html b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.html new file mode 100755 index 000000000000..ac8f06b450f2 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.html @@ -0,0 +1,41 @@ + diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js new file mode 100755 index 000000000000..fb485685bb66 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js @@ -0,0 +1,100 @@ +Template.redlinkQuery.helpers({ + hasResult(){ + const results = Template.instance().state.get('results'); + if (results) { + return results.length > 0; + } else { + return false; + } + }, + + isDirty(){ + return Template.instance().state.get('status') === 'dirty' + }, + + classExpanded(){ + const instance = Template.instance(); + return instance.state.get('resultsExpanded') ? 'expanded' : 'collapsed'; + }, + + queryPreviewHeadline(){ + const instance = Template.instance(); + const results = instance.state.get('results'); + if (results) { + const creator = results[0].creator; //all results have got the same creator + switch (creator) { + case 'community.bahn.de': + return t('results_community_bahn_de'); + case 'bahn.de': + return t('results_bahn_de'); + default: + return t('results'); + } + } + }, + + navigationOptions(){ + const instance = Template.instance(); + const results = instance.state.get('results'); + if (results) { + const creator = results[0].creator; //all results have got the same creator + let options = { + results: results, + roomId: instance.data.roomId + }; + + switch (creator) { + case 'bahn.de': + options.template = 'redlinkResultContainer_Slider'; + options.stepping = 2; + break; + case 'VKL': + options.template = 'redlinkResultContainer_Slider'; + options.stepping = 3; + break; + default: + options.template = 'redlinkResultContainer_Slider'; + options.stepping = 5; + } + return options; + } + } +}); + +Template.redlinkQuery.events({ + 'click .js-toggle-results-expanded': function (event, instance) { + const current = instance.state.get('resultsExpanded'); + instance.state.set('resultsExpanded', !current); + } +}); + +Template.redlinkQuery.onCreated(function () { + const instance = this; + + this.state = new ReactiveDict(); + this.state.setDefault({ + resultsExpanded: instance.data.query.inlineResultSupport && ( instance.data.maxConfidence === instance.data.query.confidence ), + results: [], + status: 'initial' + }); + + // Asynchronously load the results. + instance.autorun(()=> { + if (instance.data && instance.data.query && instance.data.roomId) { + //subscribe to the external messages for the room in order to re-fetch the results once the result + // of the knowledge provider changes + this.subscribe('livechat:externalMessages', Template.currentData().roomId); + + //issue a request to the redlink results-service and buffer the potential results in a reactive variable + //which then can be forwarded to the results-template + if (instance.data.query.inlineResultSupport) { + instance.state.set('status', 'dirty'); + Meteor.call('redlink:retrieveResults', instance.data.roomId, instance.data.templateIndex, instance.data.query.creator, (err, results)=> { + instance.state.set('results', results); + instance.state.set('status', 'fetched'); + }); + } + } + }) + +}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html new file mode 100755 index 000000000000..17e0d5c13487 --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html @@ -0,0 +1,16 @@ + diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.js b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.js new file mode 100755 index 000000000000..b0bf3fe9c47e --- /dev/null +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.js @@ -0,0 +1,94 @@ +/** + * Generic helper for all containers displaying results + * Might serve as superclass + */ +class redlinkResultContainerHelpers { + visibleResults() { + const instance = Template.instance(); + const results = instance.data.results; + const stepping = instance.data.stepping; + const totalLength = results.length; + + let offset = instance.state.get('currentOffset'); + if(offset >= totalLength){ + //start over immediately + offset = 0; + offset = instance.state.set('currentOffset', 0); + } + let lastElement = totalLength - 1; + if (stepping) { + lastElement = offset + stepping; + } + return results.slice(offset, lastElement); + } + + visiblePage() { + const instance = Template.instance(); + const results = instance.data.results; + const totalLength = results.length; + const offset = instance.state.get('currentOffset'); + const stepping = instance.data.stepping; + + return Math.ceil((offset/(totalLength))*(totalLength/stepping)) + 1 + } + + totalPages() { + const instance = Template.instance(); + const results = instance.data.results; + const stepping = instance.data.stepping; + + return Math.ceil(results.length/stepping) + } + + resultsCount(){ + const instance = Template.instance(); + const results = instance.data.results; + if (results) return results.length; + } + + needsNavigation(){ + const instance = Template.instance(); + const results = instance.data.results; + const stepping = instance.data.stepping; + + return (stepping < results.length); + } +} + +//----------------------------------- Slider --------------------------------------- + +Template.redlinkResultContainer_Slider.helpers(new redlinkResultContainerHelpers()); + +Template.redlinkResultContainer_Slider.events({ + + 'click .js-next-result': function (event, instance) { + const currentOffset = instance.state.get('currentOffset'); + if (currentOffset < instance.data.results.length - 1) { + instance.state.set('currentOffset', currentOffset + instance.data.stepping); + } else { + instance.state.set('currentOffset', 0); + } + }, + + 'click .js-previous-result': function (event, instance) { + const currentOffset = instance.state.get('currentOffset'); + if (currentOffset > 0) { + if (currentOffset >= instance.data.stepping) { + instance.state.set('currentOffset', currentOffset - instance.data.stepping); + } else { + instance.state.set('currentOffset', 0); + } + } else { + instance.state.set('currentOffset', instance.data.results.length - 1); + } + } +}); + +Template.redlinkResultContainer_Slider.onCreated(function () { + const instance = this; + this.state = new ReactiveDict(); + + this.state.setDefault({ + currentOffset: 0 + }); +}); diff --git a/packages/dbs-ai/dbs-ai.js b/packages/dbs-ai/dbs-ai.js new file mode 100644 index 000000000000..e925d8912bb2 --- /dev/null +++ b/packages/dbs-ai/dbs-ai.js @@ -0,0 +1,5 @@ +// Write your package code here! + +// Variables exported by this module can be imported by other packages and +// applications. See dbs-ai-tests.js for an example of importing. +export const name = 'dbs-ai'; diff --git a/packages/dbs-ai/i18n/externalSearch.de.i18n.yml b/packages/dbs-ai/i18n/externalSearch.de.i18n.yml new file mode 100755 index 000000000000..d93e536fca5f --- /dev/null +++ b/packages/dbs-ai/i18n/externalSearch.de.i18n.yml @@ -0,0 +1,33 @@ +topic_from: Von +topic_to: Nach +topic_depart: Abfahrt +topic_arrive: Ankunft +topic_product: Produkt +topic_what: Was +topic_train: Zug +topic_when: Wann +topic_start: Von +topic_end: Bis +topic_location: Ort +topic_date: Datum +topic_card: BahnCard +topic_class: Klasse +bahnDe: Bahn.de +bahn_de: Bahn.de +community_bahn_de: Service-Community +expedia: Expedia +google: Google +quixxit: Qixxit +yelp: Yelp +VKL: VKL +googleMap: Google Maps +bahnDeSearchbox: Bahn.de Suche +communityBahnDe: Service-Community +topAnsweredQuestions: Top beantwortete Fragen +maps_google-FoodAndBeverages: Googe Maps Speisen und Getränke +maps_google-WasTun: Googe Maps Suche +ApplicationHelp: Anwendungshilfe +sapTransaction: SAP Transaktionen +conversation: Vorherige Konversationen +topic_supportType: Art der Hilfe +topic_keyword: Stichwort diff --git a/packages/dbs-ai/i18n/externalSearch.en.i18n.yml b/packages/dbs-ai/i18n/externalSearch.en.i18n.yml new file mode 100755 index 000000000000..159999e142fb --- /dev/null +++ b/packages/dbs-ai/i18n/externalSearch.en.i18n.yml @@ -0,0 +1,33 @@ +topic_from: from +topic_to: to +topic_depart: depart +topic_arrive: arrive +topic_product: product +topic_what: what +topic_train: train +topic_when: when +topic_start: start +topic_end: end +topic_location: location +topic_date: date +topic_card: BahnCard +topic_class: Class +bahnDe: Bahn.de +bahn_de: Bahn.de +community_bahn_de: Service-Community +expedia: Expedia +google: Google +quixxit: Qixxit +yelp: Yelp +VKL: VKL +googleMap: Google Maps +bahnDeSearchbox: Bahn.de Search +communityBahnDe: Service-Community +topAnsweredQuestions: Top answered questions +maps_google-FoodAndBeverages: Googe Maps Food and Beverage +maps_google-WasTun: Googe Maps Suche +ApplicationHelp: Application Support +sapTransaction: SAP Transactions +conversation: Previous conversation +topic_supportType: Support type +topic_keyword: Keyword diff --git a/packages/dbs-ai/i18n/knowledgeIntegration.de.i18n.yml b/packages/dbs-ai/i18n/knowledgeIntegration.de.i18n.yml new file mode 100755 index 000000000000..f3e13c32c7c8 --- /dev/null +++ b/packages/dbs-ai/i18n/knowledgeIntegration.de.i18n.yml @@ -0,0 +1,22 @@ +knowledge_provider_usage_unknown: Die Wissensbasis... +knowledge_provider_usage_perfect: hat alles perfekt erkannt +knowledge_provider_usage_helpful: war hilfreich +knowledge_provider_usage_not_used: wurde nicht verwendet +knowledge_provider_usage_useless: war nicht hilfreich +results_community_bahn_de: Top Community Ergebnisse +results_bahn_de: Mögliche Verbindungen +results: Top Ergebnisse +duration: Dauer +track_changes: Umstiege +products: Produkte +price_low: Sparpreis +price_standard: Flexpreis +connection_details: Details +copy_to_message: Senden +connection_not_available: Verbindung nicht buchbar + +Livechat_Knowledge_Source : Wissensquelle +Livechat_Knowledge_Source_APIAI : API.ai +Livechat_Knowledge_Source_Redlink : Redlink +Livechat_Knowledge_Redlink_URL : URL des Redlink-Service +Livechat_Knowledge_Redlink_Auth_Token : Basic-Auth Token diff --git a/packages/dbs-ai/i18n/knowledgeIntegration.en.i18n.yml b/packages/dbs-ai/i18n/knowledgeIntegration.en.i18n.yml new file mode 100755 index 000000000000..431e93336c52 --- /dev/null +++ b/packages/dbs-ai/i18n/knowledgeIntegration.en.i18n.yml @@ -0,0 +1,22 @@ +knowledge_provider_usage_unknown: Unknown +knowledge_provider_usage_perfect: Perfectly recognized +knowledge_provider_usage_helpful: Helpful +knowledge_provider_usage_not_used: Not used +knowledge_provider_usage_useless: Not helpful +results_community_bahn_de: Top community results +results_bahn_de: Possible connections +results: Top results +duration: Duration +track_changes: Track changes +products: Products +price_low: Standard price +price_standard: Flex price +connection_details: Details +copy_to_message: Send +connection_not_available: Connection not available + +Livechat_Knowledge_Source : Knowledge source +Livechat_Knowledge_Source_APIAI : API.ai +Livechat_Knowledge_Source_Redlink : Redlink +Livechat_Knowledge_Redlink_URL : URL of Redlink service +Livechat_Knowledge_Redlink_Auth_Token : Basic-Auth token diff --git a/packages/dbs-ai/package.js b/packages/dbs-ai/package.js new file mode 100755 index 000000000000..53467f1dfe90 --- /dev/null +++ b/packages/dbs-ai/package.js @@ -0,0 +1,49 @@ +Package.describe({ + name: 'dbs:ai', + version: '0.0.1', + summary: 'Integration of artifical knowledge', + git: '', //not hosted on separaete git repo yet - use http://github.com/mrsimpson/Rocket.Chat + documentation: 'README.md' +}); + +function addDirectory(api, pathInPackage, environment) { + const PACKAGE_PATH = 'packages/dbs-ai/'; + const _ = Npm.require('underscore'); + const fs = Npm.require('fs'); + const files = _.compact(_.map(fs.readdirSync(PACKAGE_PATH + pathInPackage), function (filename) { + return pathInPackage + '/' + filename + })); + api.addFiles(files, environment); +} + +Package.onUse(function (api) { + + api.use(['ecmascript', 'underscore']); + api.use('templating', 'client'); + api.use('less@2.5.1'); + api.use('rocketchat:lib'); + + api.addAssets('assets/stylesheets/redlink.less', 'server'); + + api.addAssets('assets/icons/sapTransaction.png', 'client'); + api.addAssets('assets/icons/peerToPeerHelp.png', 'client'); + api.addAssets('assets/icons/communication.png', 'client'); + + api.addFiles('server/config.js', 'server'); + addDirectory(api, 'server/methods', 'server'); + addDirectory(api, 'server/lib', 'server'); + addDirectory(api, 'server/hooks', 'server'); + + api.addFiles('client/redlink_ui.js', 'client'); + addDirectory(api,'client/views/app/tabbar', 'client'); + + //i18n + var _ = Npm.require('underscore'); + var fs = Npm.require('fs'); + var tapi18nFiles = _.compact(_.map(fs.readdirSync('packages/dbs-ai/i18n'), function(filename) { + return 'i18n/' + filename; + })); + api.addFiles(tapi18nFiles); + + api.use('tap:i18n'); +}); diff --git a/packages/dbs-ai/server/config.js b/packages/dbs-ai/server/config.js new file mode 100755 index 000000000000..aa3f8cf85797 --- /dev/null +++ b/packages/dbs-ai/server/config.js @@ -0,0 +1,35 @@ +Meteor.startup(function () { + RocketChat.settings.add('Livechat_Knowledge_Source', '', { + type: 'select', + group: 'Reisebuddy', + section: 'Knowledge Base', + values: [ + { key: '0', i18nLabel: 'Livechat_Knowledge_Source_APIAI'}, + { key: '1', i18nLabel: 'Livechat_Knowledge_Source_Redlink'} + ], + public: true, + i18nLabel: 'Livechat_Knowledge_Source' + }); + + RocketChat.settings.add('Livechat_Knowledge_Redlink_URL', '', { + type: 'string', + group: 'Reisebuddy', + section: 'Knowledge Base', + public: true, + i18nLabel: 'Livechat_Knowledge_Redlink_URL' + }); + + /* Currently, Redlink does not offer hashed API_keys, but uses simple password-auth + * This is of course far from perfect and is hopeully going to change sometime later */ + RocketChat.settings.add('Livechat_Knowledge_Redlink_Auth_Token', '', { + type: 'string', + group: 'Reisebuddy', + section: 'Knowledge Base', + public: true, + i18nLabel: 'Livechat_Knowledge_Redlink_Auth_Token' + }); +}); + +RocketChat.theme.addPackageAsset(() => { + return Assets.getText('assets/stylesheets/redlink.less'); +}); diff --git a/packages/dbs-ai/server/hooks/closeLivechatKnowledgeAdapter.js b/packages/dbs-ai/server/hooks/closeLivechatKnowledgeAdapter.js new file mode 100755 index 000000000000..045ff553863c --- /dev/null +++ b/packages/dbs-ai/server/hooks/closeLivechatKnowledgeAdapter.js @@ -0,0 +1,20 @@ +/** + * Notifies the knowledgeProvider about the end of a livechat conversation + */ +RocketChat.callbacks.add('closeReisebuddyLivechat', function (room, closeProps) { + try { + const knowledgeAdapter = _dbs.getKnowledgeAdapter(); + if (knowledgeAdapter && knowledgeAdapter.onClose) { + knowledgeAdapter.onClose(room); + } else { + SystemLogger.warn('No knowledge provider configured'); + } + } catch (e) { + SystemLogger.error('Error submitting closed conversation to knowledge provider ->', e); + } + + let updatedRBInfo = room.rbInfo ? room.rbInfo : {}; + updatedRBInfo.knowledgeProviderUsage = closeProps.knowledgeProviderUsage; + RocketChat.models.Rooms.update(room._id, {$set: {rbInfo: updatedRBInfo}}); + +}, RocketChat.callbacks.priority.LOW); diff --git a/packages/dbs-ai/server/hooks/sendMessageToKnowledgeAdapter.js b/packages/dbs-ai/server/hooks/sendMessageToKnowledgeAdapter.js new file mode 100755 index 000000000000..2a9da55d57e1 --- /dev/null +++ b/packages/dbs-ai/server/hooks/sendMessageToKnowledgeAdapter.js @@ -0,0 +1,42 @@ +/* globals SystemLogger */ + +RocketChat.callbacks.add('afterSaveMessage', function (message, room) { + // skips this callback if the message was edited + if (message.editedAt) { + return message; + } + + let knowledgeEnabled = false; + RocketChat.settings.get('Livechat_Knowledge_Enabled', function (key, value) { + knowledgeEnabled = value; + }); + + if (!knowledgeEnabled) { + return message; + } + + if (!(typeof room.t !== 'undefined' && room.v && room.v.token)) { + return message; + } + + // if the message hasn't a token, it was not sent by the visitor, so ignore it + if (!message.token) { + return message; + } + + const knowledgeAdapter = _dbs.getKnowledgeAdapter(); + if (!knowledgeAdapter) { + return; + } + + Meteor.defer(() => { + try { + knowledgeAdapter.onMessage(message); + } + catch (e) { + SystemLogger.error('Error using knowledge provider ->', e); + } + }); + + return message; +}, RocketChat.callbacks.priority.LOW); diff --git a/packages/dbs-ai/server/lib/AiApiAdapter.js b/packages/dbs-ai/server/lib/AiApiAdapter.js new file mode 100755 index 000000000000..f2ce45bf38cd --- /dev/null +++ b/packages/dbs-ai/server/lib/AiApiAdapter.js @@ -0,0 +1,33 @@ +class ApiAiAdapter { + constructor(adapterProps) { + this.properties = adapterProps; + this.headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer ' + this.properties.token + } + } + + onMessage(message) { + const responseAPIAI = HTTP.post(this.properties.url, { + data: { + query: message.msg, + lang: this.properties.language + }, + headers: this.headers + }); + if (responseAPIAI.data && responseAPIAI.data.status.code === 200 && !_.isEmpty(responseAPIAI.data.result.fulfillment.speech)) { + RocketChat.models.LivechatExternalMessage.insert({ + rid: message.rid, + msg: responseAPIAI.data.result.fulfillment.speech, + orig: message._id, + ts: new Date() + }); + } + } + + onClose() { + //do nothing, api.ai does not learn from us. + } +} + +_dbs.ApiAiAdapterClass = ApiAiAdapter; diff --git a/packages/dbs-ai/server/lib/KnowledgeAdapterProvider.js b/packages/dbs-ai/server/lib/KnowledgeAdapterProvider.js new file mode 100755 index 000000000000..b2df37d5b749 --- /dev/null +++ b/packages/dbs-ai/server/lib/KnowledgeAdapterProvider.js @@ -0,0 +1,54 @@ +_dbs.getKnowledgeAdapter = function () { + var knowledgeSource = ''; + + const KNOWLEDGE_SRC_APIAI = "0"; + const KNOWLEDGE_SRC_REDLINK = "1"; + + RocketChat.settings.get('Livechat_Knowledge_Source', function (key, value) { + knowledgeSource = value; + }); + + let adapterProps = { + url: '', + token: '', + language: '' + }; + + switch (knowledgeSource) { + case KNOWLEDGE_SRC_APIAI: + adapterProps.url = 'https://api.api.ai/api/query?v=20150910'; + + RocketChat.settings.get('Livechat_Knowledge_Apiai_Key', function (key, value) { + adapterProps.token = value; + }); + RocketChat.settings.get('Livechat_Knowledge_Apiai_Language', function (key, value) { + adapterProps.language = value; + }); + + if (!_dbs.apiaiAdapter) { + _dbs.apiaiAdapter = new _dbs.ApiAiAdapterClass(adapterProps); + } + return _dbs.apiaiAdapter; + break; + case KNOWLEDGE_SRC_REDLINK: + return _dbs.RedlinkAdapterFactory.getInstance(); // buffering done inside the factory method + break; + } +}; + +/** + * Refreshes the adapter instances on change of the configuration - the redlink-adapter factory does that on its own + */ +Meteor.autorun(()=> { + RocketChat.settings.get('Livechat_Knowledge_Source', function (key, value) { + _dbs.apiaiAdapter = undefined; + }); + + RocketChat.settings.get('Livechat_Knowledge_Apiai_Key', function (key, value) { + _dbs.apiaiAdapter = undefined; + }); + + RocketChat.settings.get('Livechat_Knowledge_Apiai_Language', function (key, value) { + _dbs.apiaiAdapter = undefined; + }); +}); diff --git a/packages/dbs-ai/server/lib/Redlink.js b/packages/dbs-ai/server/lib/Redlink.js new file mode 100755 index 000000000000..17116640a789 --- /dev/null +++ b/packages/dbs-ai/server/lib/Redlink.js @@ -0,0 +1,290 @@ +class RedlinkAdapter { + constructor(adapterProps) { + this.properties = adapterProps; + this.headers = {}; + this.headers['content-Type'] = 'application/json; charset=utf-8'; + if (this.properties.token) { + this.headers['authorization'] = 'basic ' + this.properties.token; + } + } + + createRedlinkStub(rid, latestKnowledgeProviderResult) { + const latestRedlinkResult = (latestKnowledgeProviderResult && latestKnowledgeProviderResult.knowledgeProvider === 'redlink') + ? latestKnowledgeProviderResult.result + : {}; + return { + id: latestRedlinkResult.id ? latestRedlinkResult.id : rid, + meta: latestRedlinkResult.meta ? latestRedlinkResult.meta : {}, + user: latestRedlinkResult.user ? latestRedlinkResult.user : {}, + messages: latestRedlinkResult.messages ? latestRedlinkResult.messages : [], + tokens: latestRedlinkResult.tokens ? latestRedlinkResult.tokens : [], + queryTemplates: latestRedlinkResult.queryTemplates ? latestRedlinkResult.queryTemplates : [] + } + } + + getConversation(rid, latestKnowledgeProviderResult) { + + let analyzedUntil = 0; + let conversation = []; + + if (latestKnowledgeProviderResult && latestKnowledgeProviderResult.knowledgeProvider === 'redlink') { + //there might have been another provider configures, e. g. if API.ai was entered earlier + // therefore we need to validate we're operating with a Redlink-result + + analyzedUntil = latestKnowledgeProviderResult.originMessage ? latestKnowledgeProviderResult.originMessage.ts : 0; + conversation = latestKnowledgeProviderResult.result.messages ? latestKnowledgeProviderResult.result.messages : []; + } + + const room = RocketChat.models.Rooms.findOneById(rid); + RocketChat.models.Messages.find({ + rid: rid, + _hidden: {$ne: true}, + ts: {$gt: new Date(analyzedUntil)} + }).forEach(visibleMessage => { + conversation.push({ + content: visibleMessage.msg, + time: visibleMessage.ts, + origin: (room.v._id === visibleMessage.u._id) ? 'User' : 'Agent' //in livechat, the owner of the room is the user + }); + }); + return conversation; + } + + onResultModified(modifiedRedlinkResult) { + try { + SystemLogger.debug("sending update to redlinkk with: " + JSON.stringify(modifiedRedlinkResult)); + const responseRedlinkQuery = HTTP.post(this.properties.url + '/query', { + data: modifiedRedlinkResult.result, + headers: this.headers + }); + SystemLogger.debug("recieved update to redlinkk with: " + JSON.stringify(responseRedlinkQuery)); + RocketChat.models.LivechatExternalMessage.update( + { + _id: modifiedRedlinkResult._id + }, + { + $set: { + result: responseRedlinkQuery.data + }, + $unset: { + inlineResults: "" + } + }); + + } catch (err) { + console.error('Updating redlink results (via QUERY) did not succeed -> ', JSON.stringify(err)); + } + } + + onMessage(message, context = {}) { + const knowledgeProviderResultCursor = this.getKnowledgeProviderCursor(message.rid); + const latestKnowledgeProviderResult = knowledgeProviderResultCursor.fetch()[0]; + + const requestBody = this.createRedlinkStub(message.rid, latestKnowledgeProviderResult); + requestBody.messages = this.getConversation(message.rid, latestKnowledgeProviderResult); + + requestBody.context = context; + + try { + + const responseRedlinkPrepare = HTTP.post(this.properties.url + '/prepare', { + data: requestBody, + headers: this.headers + }); + + if (responseRedlinkPrepare.data && responseRedlinkPrepare.statusCode === 200) { + + this.purgePreviousResults(knowledgeProviderResultCursor); + + const externalMessageId = RocketChat.models.LivechatExternalMessage.insert({ + rid: message.rid, + knowledgeProvider: "redlink", + originMessage: {_id: message._id, ts: message.ts}, + result: responseRedlinkPrepare.data, + ts: new Date() + }); + + const externalMessage = RocketChat.models.LivechatExternalMessage.findOneById(externalMessageId); + + Meteor.defer(() => RocketChat.callbacks.run('afterExternalMessage', externalMessage)); + } + } catch (e) { + console.error('Redlink-Prepare/Query with results from prepare did not succeed -> ', e); + } + } + + getQueryResults(roomId, templateIndex, creator) { + // ---------------- private methods + const _getKeyForBuffer = function (templateIndex, creator) { + return templateIndex + '-' + creator.replace(/\./g, '_'); + }; + + const _getBufferedResults = function (latestKnowledgeProviderResult, templateIndex, creator) { + if (latestKnowledgeProviderResult && latestKnowledgeProviderResult.knowledgeProvider === 'redlink' && latestKnowledgeProviderResult.inlineResults) { + return latestKnowledgeProviderResult.inlineResults[_getKeyForBuffer(templateIndex, creator)]; + } + }; + // ---------------- private methods + + var results = []; + + const latestKnowledgeProviderResult = this.getKnowledgeProviderCursor(roomId).fetch()[0]; + + if (latestKnowledgeProviderResult) { + results = _getBufferedResults(latestKnowledgeProviderResult, templateIndex, creator); + } else { + return []; // If there was no knowledge-provider-result, there cannot be any results either + } + + if (!results) { + try { + const request = { + data: { + messages: latestKnowledgeProviderResult.result.messages, + tokens: latestKnowledgeProviderResult.result.tokens, + queryTemplates: latestKnowledgeProviderResult.result.queryTemplates, + context: latestKnowledgeProviderResult.result.context + }, + headers: this.headers + }; + + const responseRedlinkResult = HTTP.post(this.properties.url + '/result/' + creator + '/?templateIdx=' + templateIndex, request); + if (responseRedlinkResult.data && responseRedlinkResult.statusCode === 200) { + results = responseRedlinkResult.data; + + if (creator === 'conversation') { + results.forEach(function (result) { + // Some dirty string operations to convert the snippet to javascript objects + let transformedSnippet = JSON.stringify(result.snippet); + transformedSnippet = transformedSnippet.slice(1, transformedSnippet.length - 1); //remove quotes in the beginning and at the end + + if (transformedSnippet) { + transformedSnippet = '[' + transformedSnippet; + transformedSnippet = transformedSnippet.replace(/\\n/g, ''); + transformedSnippet = transformedSnippet.replace(/
/g, '{"origin": "seeker", "text": "'); + transformedSnippet = transformedSnippet.replace(/
/g, '{"origin": "provider", "text": "'); + transformedSnippet = transformedSnippet.replace(/<\/div>/g, '"},'); + transformedSnippet = transformedSnippet.trim(); + if (transformedSnippet.endsWith(',')) { + transformedSnippet = transformedSnippet.slice(0, transformedSnippet.length - 1); + } + transformedSnippet = transformedSnippet + ']'; + } + try { + const messages = JSON.parse(transformedSnippet); + result.messages = messages; + } catch(err){ + console.error('Error parsing conversation',err) + } + }); + results.reduce((result)=>!!result.messages); + } + + //buffer the results + let inlineResultsMap = latestKnowledgeProviderResult.inlineResults || {}; + inlineResultsMap[_getKeyForBuffer(templateIndex, creator)] = results; + + RocketChat.models.LivechatExternalMessage.update( + { + _id: latestKnowledgeProviderResult._id + }, + { + $set: { + inlineResults: inlineResultsMap + } + }); + + } else { + console.error("Couldn't read result from Redlink"); + } + } catch (err) { + console.error('Retrieving Query-resuls from Redlink did not succeed -> ', err); + } + } + return results; + } + + purgePreviousResults(knowledgeProviderResultCursor) { + //delete suggestions proposed so far - Redlink will always analyze the complete conversation + knowledgeProviderResultCursor.forEach((oldSuggestion) => { + RocketChat.models.LivechatExternalMessage.remove(oldSuggestion._id); + }); + } + + getKnowledgeProviderCursor(roomId) { + return RocketChat.models.LivechatExternalMessage.findByRoomId(roomId, {ts: -1}); + } + + onClose(room) { //async + + const knowledgeProviderResultCursor = this.getKnowledgeProviderCursor(room._id); + let latestKnowledgeProviderResult = knowledgeProviderResultCursor.fetch()[0]; + if (latestKnowledgeProviderResult) { + latestKnowledgeProviderResult.helpful = room.rbInfo.knowledgeProviderUsage; + + HTTP.post(this.properties.url + '/store', { + data: { + latestKnowledgeProviderResult + }, + headers: this.headers + }); + } + } +} + +class RedlinkMock extends RedlinkAdapter { + constructor(adapterProps) { + super(adapterProps); + + this.properties.url = 'http://localhost:8080'; + delete this.headers.authorization; + } +} + +class RedlinkAdapterFactory { + constructor() { + this.singleton = undefined; + + /** + * Refreshes the adapter instances on change of the configuration + */ + Meteor.autorun(()=> { + RocketChat.settings.get('Livechat_Knowledge_Source', function (key, value) { + this.singleton = undefined; + }); + + RocketChat.settings.get('Livechat_Knowledge_Redlink_URL', function (key, value) { + this.singleton = undefined; + }); + + RocketChat.settings.get('Livechat_Knowledge_Redlink_Auth_Token', function (key, value) { + this.singleton = undefined; + }); + }); + }; + + static getInstance() { + if (this.singleton) { + return this.singleton + } else { + var adapterProps = { + url: '', + token: '', + language: '' + }; + + adapterProps.url = RocketChat.settings.get('Livechat_Knowledge_Redlink_URL'); + + adapterProps.token = RocketChat.settings.get('Livechat_Knowledge_Redlink_Auth_Token'); + + if (_dbs.mockInterfaces()) { //use mock + this.singleton = new RedlinkMock(adapterProps); + } else { + this.singleton = new RedlinkAdapter(adapterProps); + } + return this.singleton; + } + } +} + +_dbs.RedlinkAdapterFactory = RedlinkAdapterFactory; diff --git a/packages/dbs-ai/server/methods/retrieveResults.js b/packages/dbs-ai/server/methods/retrieveResults.js new file mode 100755 index 000000000000..bf510e1cef7f --- /dev/null +++ b/packages/dbs-ai/server/methods/retrieveResults.js @@ -0,0 +1,82 @@ +Meteor.methods({ + 'redlink:retrieveResults'(roomId, templateIndex, creator){ + const adapter = _dbs.RedlinkAdapterFactory.getInstance(); + results = adapter.getQueryResults(roomId, templateIndex, creator); + + return results; + // + // ein paar offline-fähige Testdaten + // return [ + // { + // "replySuggestion": "Sie können sich eine neue Bahncard zusenden lassen", + // "offer": "38210", + // "title": "Jugend BahnCard 25 ", + // "categories": [ + // "BahnCard" + // ], + // "body": "e Angaben in der endgültigen Jugend BahnCard 25 BC-Service anrufen und Zusendung einer neuen Jugend BC 25 veranlassen. Auffinden einer Jugend BahnCard 25 Gefundene Jugend BahnCard 25 an BahnCard Service senden Umtausch/Erstattung Ausgeschlossen Auch der Umtausch einer BahnCard 25 Zusatz (Kind) in eine Jugend BahnCard 25 ist ausgeschlossen. Vergessene Jugend BahnCard 25 Verfahren analog BahnCard 25 Verlust der vorläufigen bzw. endgültigen Jugend BC 25 Keine Ersatzausstellung Kunde kann eine neue Jugend BahnCard 25 zum Preis von 10 EUR erwerben Hintergrundinfo Jugend BahnCards 25 werden ohne Passfoto ausgestellt und sind für Inhaber ab 16 Jahre im Zug nur mit einem amtlichen Lichtbildausweis gültig. Die BahnCard Jugend 25 wird bei der Bestellung einer BahnCard 25 für Familien (Haupt- und Zusatzkarten) nicht als BahnCard 25 Zusatzkarte Kind anerkannt. Der Jugend BahnCard 25-Rabatt wird für Fahrkarten 1. und 2. Klasse gewährt Eine Kündigung ...
.... Jugend BahnCard 25 4398 Muster Jugend BahnCard 25 ", + // "link": "http://www.dbportal.db.de/scripts/cgiip.exe/VKL/Sichten/XMLAusgabe.w?RegelwerkNr=38210&AufrufVon=VKL&User=VKL", + // "score": 221.85854, + // "creator": "VKL", + // "topic": "Produkt" + // }, + // { + // "replySuggestion": null, + // "offer": "37974", + // "title": "Probe BahnCard 25 ", + // "categories": [ + // "BahnCard" + // ], + // "body": "nCard 25 als Folge-BahnCard ist bereits beim Kauf der Probe BahnCard 25 der jeweils notwendige Nachweis erforderlich und entsprechend im Probe BC 25-Bestellschein anzukreuzen Umtausch/Erstattung Ausgeschlossen Kündigung Analog BahnCard 25 Vergessene Probe BahnCard 25 Analog BahnCard 25 Verlust der vorläufigen / endgültigen Probe BahnCard 25 Analog der regulärenBahnCard 25 über den BahnCard-Service gegen ein Entgelt von 15 EUR zugelassen ERV Verkauf der Versicherung nur zeitgleich beim Kauf der Probe BahnCard 25 zugelassen Ein nachträglicher Verkauf der Aktions- und Probe BahnCard-Versicherung ist ausgeschlossen Eingabehilfe Probe BahnCard 25 Leistungskatalog Klasse Leistungs-ID Vorl. Probe BahnCard 25 2./1.Klasse 4369 Muster 2. Klasse 1. Klasse", + // "link": "http://www.dbportal.db.de/scripts/cgiip.exe/VKL/Sichten/XMLAusgabe.w?RegelwerkNr=37974&AufrufVon=VKL&User=VKL", + // "score": 221.68828, + // "creator": "VKL", + // "topic": "Produkt" + // } + // ]; + // return [ + // { + // "replySuggestion": "Um 22:25  Uhr gibt es eine Verbindung von Frankfurt(Main)Hbf nach Paris Est. Du wärst dann um 07:50  dort.", + // "departure": { + // "location": "Frankfurt(Main)Hbf", + // "time": "22:25 " + // }, + // "arrival": { + // "location": "Paris Est", + // "time": "07:50 " + // }, + // "dateChange": "+ 1 Tag", + // "travelDuration": 565, + // "travelChanges": 3, + // "travelProducts": [ + // "RE", + // "RE", + // "TER", + // "TGV" + // ], + // "creator": "bahn.de", + // "topic": "Reiseplanung" + // }, + // { + // "replySuggestion": "Um 02:48  Uhr gibt es eine Verbindung von Frankfurt(Main)Hbf nach Paris Est. Du wärst dann um 09:07  dort.", + // "departure": { + // "location": "Frankfurt(Main)Hbf", + // "time": "02:48 " + // }, + // "arrival": { + // "location": "Paris Est", + // "time": "09:07 " + // }, + // "travelDuration": 379, + // "travelChanges": 2, + // "travelProducts": [ + // "IC", + // "SWE", + // "TGV" + // ], + // "creator": "bahn.de", + // "topic": "Reiseplanung" + // } + // ]; + } +}); diff --git a/packages/dbs-ai/server/methods/updateKnowledgeProviderResult.js b/packages/dbs-ai/server/methods/updateKnowledgeProviderResult.js new file mode 100755 index 000000000000..35da5cf7b008 --- /dev/null +++ b/packages/dbs-ai/server/methods/updateKnowledgeProviderResult.js @@ -0,0 +1,14 @@ +Meteor.methods({ + 'updateKnowledgeProviderResult': function (modifiedKnowledgeProviderResult) { + if (!modifiedKnowledgeProviderResult) { + return; + } + + const knowledgeAdapter = _dbs.getKnowledgeAdapter(); + + if (knowledgeAdapter instanceof _dbs.RedlinkAdapterFactory.getInstance().constructor && + modifiedKnowledgeProviderResult.knowledgeProvider === 'redlink') { + return knowledgeAdapter.onResultModified(modifiedKnowledgeProviderResult); + } + } +}); diff --git a/packages/dbs-common/README.md b/packages/dbs-common/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/dbs-common/client/lib/globalTemplateHelpers.js b/packages/dbs-common/client/lib/globalTemplateHelpers.js new file mode 100755 index 000000000000..2f1f2b41537e --- /dev/null +++ b/packages/dbs-common/client/lib/globalTemplateHelpers.js @@ -0,0 +1,21 @@ +Template.registerHelper('and', (a, b)=> a && b); +Template.registerHelper('or', (a, b)=> a || b); + +/** + * Allows to access reactive dict components in Blaze-templates: {{instance.state.get "foo"}} + */ +Template.registerHelper('instance', ()=> Template.instance()); + +Template.registerHelper('arrayLength', (array) => array.length); + +Template.registerHelper('add', (a, b) => a + b); + +Template.registerHelper('text', (i18n_alias) => t(i18n_alias)); + +Template.registerHelper('isReisebuddy', () => !!RocketChat.settings.get('Reisebuddy_active')); + +Template.registerHelper('formatDateMilliseconds', (val) => new _dbs.Duration(val).toHHMMSS()); + +Template.registerHelper('templateExists', (val) => !!Template[val]); + +Template.registerHelper('floatToFixed', (size, val) => val ? val.toFixed(size) : ''); diff --git a/packages/dbs-common/dbs-common.js b/packages/dbs-common/dbs-common.js new file mode 100644 index 000000000000..191e37714fd5 --- /dev/null +++ b/packages/dbs-common/dbs-common.js @@ -0,0 +1,5 @@ +// Write your package code here! + +// Variables exported by this module can be imported by other packages and +// applications. See dbs-common-tests.js for an example of importing. +export const name = 'dbs-common'; diff --git a/packages/dbs-common/lib/core.js b/packages/dbs-common/lib/core.js new file mode 100755 index 000000000000..b5fff1362e22 --- /dev/null +++ b/packages/dbs-common/lib/core.js @@ -0,0 +1,2 @@ +/* exported _dbs */ +_dbs = {}; diff --git a/packages/dbs-common/lib/duration.js b/packages/dbs-common/lib/duration.js new file mode 100755 index 000000000000..c9b758b0df17 --- /dev/null +++ b/packages/dbs-common/lib/duration.js @@ -0,0 +1,23 @@ +/* globals _dbs */ + +class Duration { + constructor(ms) { + this.ms = ms; + this.date = new Date(ms); + } + + static padZero(i) { + return ( i < 10 ? "0" + i : i ); + } + + toHHMMSS() { + return Math.floor(this.ms / 3600000) + ':' + Duration.padZero(this.date.getMinutes()) + ':' + + Duration.padZero(this.date.getSeconds()) + } + + toMM() { + return Duration.padZero(Math.floor(this.ms / 60000)); + } +} + +_dbs.Duration = Duration; diff --git a/packages/dbs-common/lib/testing.js b/packages/dbs-common/lib/testing.js new file mode 100755 index 000000000000..136f0fc0760a --- /dev/null +++ b/packages/dbs-common/lib/testing.js @@ -0,0 +1,3 @@ +_dbs.mockInterfaces = function(){ + return false; +}; diff --git a/packages/dbs-common/package.js b/packages/dbs-common/package.js new file mode 100755 index 000000000000..f8e2a9667fc1 --- /dev/null +++ b/packages/dbs-common/package.js @@ -0,0 +1,23 @@ +Package.describe({ + name: 'dbs:common', + version: '0.0.1', + summary: 'Basic customizing for db', // Brief, one-line summary of the package. + git: '', + documentation: '' +}); + + +Package.onUse(function (api) { + api.use(['ecmascript', 'underscore']); + api.use('templating', 'client'); //needed in order to be able to register global helpers on the Template-object + + api.addFiles('lib/core.js'); + api.addFiles('lib/duration.js', 'client'); + api.addFiles('lib/testing.js', 'server'); + api.addFiles('client/lib/globalTemplateHelpers.js', 'client'); + + api.addFiles('server/config.js', 'server'); + api.addFiles('server/customHttpsCerts.js', 'server'); + + api.export('_dbs'); +}); diff --git a/packages/dbs-common/server/config.js b/packages/dbs-common/server/config.js new file mode 100755 index 000000000000..915098b73271 --- /dev/null +++ b/packages/dbs-common/server/config.js @@ -0,0 +1,11 @@ +Meteor.startup(function () { + RocketChat.settings.addGroup('Reisebuddy'); + + RocketChat.settings.add('Reisebuddy_active', true, { + type: 'boolean', + group: 'Reisebuddy', + section: 'General', + 'public': true, + i18nLabel: 'Reisebuddy_active' + }); +}); diff --git a/packages/dbs-common/server/customHttpsCerts.js b/packages/dbs-common/server/customHttpsCerts.js new file mode 100755 index 000000000000..151f3926b22b --- /dev/null +++ b/packages/dbs-common/server/customHttpsCerts.js @@ -0,0 +1,44 @@ +/** + * Hack to apply own certs + */ +(function () { + var https = Npm.require('https'); + var certDir = process.env.CA_CERT_PATH; + + if (!certDir) { //backwards compatible + certDir = process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'] + '/.nodeCaCerts/'; + } + + var caMap = (function () { + try { + var fs = Npm.require('fs'); + var result = {}; + if (fs.statSync(certDir).isDirectory()) { + var certList = fs.readdirSync(certDir); + for (var i = 0; i < certList.length; i++) { + result[certList[i]] = fs.readFileSync(certDir + certList[i]); + console.info('Loaded certificate ' + certList[i]); + } + } + + console.info("HTTP-Proxy", process.env.HTTP_PROXY); + console.info("HTTPS-Proxy", process.env.HTTPS_PROXY); + console.info("No Proxy", process.env.NO_PROXY); + } catch (e) { + console.warn("unable to load private root certs from path: " + certDir, e); + } + return result; + })(); + https.request = (function (request) { + return function (options, cb) { + if (options && !options.ca) { + var crt = caMap[options.hostname || options.host]; + if(crt) { + options.ca = caMap[options.hostname || options.host]; + console.info("Issuing secured request to ", (options.hostname || options.host)); + } + } + return request.call(https, options, cb); + }; + })(https.request); +})(); From 3c555d659eaef6244f438a17bb315150a5e87fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Tue, 14 Feb 2017 14:02:47 +0100 Subject: [PATCH 02/83] manual copy of ai and common re-use stuff from p2phelp --- .meteor/versions | 2 ++ packages/dbs-ai/package.js | 1 + 2 files changed, 3 insertions(+) diff --git a/.meteor/versions b/.meteor/versions index b4ab41494d62..ebf6737e33db 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -24,6 +24,8 @@ cfs:http-methods@0.0.32 check@1.2.4 coffeescript@1.11.1_4 dandv:caret-position@2.1.1 +dbs:ai@0.0.1 +dbs:common@0.0.1 ddp@1.2.5 ddp-client@1.3.2 ddp-common@1.2.8 diff --git a/packages/dbs-ai/package.js b/packages/dbs-ai/package.js index 53467f1dfe90..c80733cd8fe5 100755 --- a/packages/dbs-ai/package.js +++ b/packages/dbs-ai/package.js @@ -22,6 +22,7 @@ Package.onUse(function (api) { api.use('templating', 'client'); api.use('less@2.5.1'); api.use('rocketchat:lib'); + api.use('dbs:common'); api.addAssets('assets/stylesheets/redlink.less', 'server'); From 34d4f63eb7bf16cb38c6981e8778f236a21d9180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20J=C3=A4gle?= Date: Sat, 18 Feb 2017 20:08:07 +0100 Subject: [PATCH 03/83] Use recent node.js authentication: Pass client certificate in options, remove hack which existed for node 0.10.xx --- packages/dbs-ai/server/lib/Redlink.js | 50 ++++++++++--------- packages/dbs-common/package.js | 1 - .../dbs-common/server/customHttpsCerts.js | 44 ---------------- 3 files changed, 26 insertions(+), 69 deletions(-) delete mode 100755 packages/dbs-common/server/customHttpsCerts.js diff --git a/packages/dbs-ai/server/lib/Redlink.js b/packages/dbs-ai/server/lib/Redlink.js index 17116640a789..7262ed9f816f 100755 --- a/packages/dbs-ai/server/lib/Redlink.js +++ b/packages/dbs-ai/server/lib/Redlink.js @@ -1,10 +1,16 @@ class RedlinkAdapter { constructor(adapterProps) { this.properties = adapterProps; - this.headers = {}; - this.headers['content-Type'] = 'application/json; charset=utf-8'; + this.properties.url = this.properties.url.toLowerCase(); + + this.options = {}; + this.options.headers={}; + this.options.headers['content-Type'] = 'application/json; charset=utf-8'; if (this.properties.token) { - this.headers['authorization'] = 'basic ' + this.properties.token; + this.options.headers['authorization'] = 'basic ' + this.properties.token; + } + if(this.properties.url.substring(0, 4) === 'https'){ + this.options.cert = '~/.nodeCaCerts/' + this.properties.url.replace('https', ''); } } @@ -53,10 +59,9 @@ class RedlinkAdapter { onResultModified(modifiedRedlinkResult) { try { SystemLogger.debug("sending update to redlinkk with: " + JSON.stringify(modifiedRedlinkResult)); - const responseRedlinkQuery = HTTP.post(this.properties.url + '/query', { - data: modifiedRedlinkResult.result, - headers: this.headers - }); + let options = this.options; + options.data = modifiedRedlinkResult.result; + const responseRedlinkQuery = HTTP.post(this.properties.url + '/query', options); SystemLogger.debug("recieved update to redlinkk with: " + JSON.stringify(responseRedlinkQuery)); RocketChat.models.LivechatExternalMessage.update( { @@ -86,11 +91,9 @@ class RedlinkAdapter { requestBody.context = context; try { - - const responseRedlinkPrepare = HTTP.post(this.properties.url + '/prepare', { - data: requestBody, - headers: this.headers - }); + let options = this.options; + this.options.data = requestBody; + const responseRedlinkPrepare = HTTP.post(this.properties.url + '/prepare', options); if (responseRedlinkPrepare.data && responseRedlinkPrepare.statusCode === 200) { @@ -138,17 +141,18 @@ class RedlinkAdapter { if (!results) { try { - const request = { - data: { + + let options = this.options; + this.options.data = this.options; + + options.data = { messages: latestKnowledgeProviderResult.result.messages, tokens: latestKnowledgeProviderResult.result.tokens, queryTemplates: latestKnowledgeProviderResult.result.queryTemplates, context: latestKnowledgeProviderResult.result.context - }, - headers: this.headers - }; + }; - const responseRedlinkResult = HTTP.post(this.properties.url + '/result/' + creator + '/?templateIdx=' + templateIndex, request); + const responseRedlinkResult = HTTP.post(this.properties.url + '/result/' + creator + '/?templateIdx=' + templateIndex, options); if (responseRedlinkResult.data && responseRedlinkResult.statusCode === 200) { results = responseRedlinkResult.data; @@ -222,12 +226,10 @@ class RedlinkAdapter { if (latestKnowledgeProviderResult) { latestKnowledgeProviderResult.helpful = room.rbInfo.knowledgeProviderUsage; - HTTP.post(this.properties.url + '/store', { - data: { - latestKnowledgeProviderResult - }, - headers: this.headers - }); + let options = this.options; + this.options.data = latestKnowledgeProviderResult; + + HTTP.post(this.properties.url + '/store', options); } } } diff --git a/packages/dbs-common/package.js b/packages/dbs-common/package.js index f8e2a9667fc1..2e344ea6cc0e 100755 --- a/packages/dbs-common/package.js +++ b/packages/dbs-common/package.js @@ -17,7 +17,6 @@ Package.onUse(function (api) { api.addFiles('client/lib/globalTemplateHelpers.js', 'client'); api.addFiles('server/config.js', 'server'); - api.addFiles('server/customHttpsCerts.js', 'server'); api.export('_dbs'); }); diff --git a/packages/dbs-common/server/customHttpsCerts.js b/packages/dbs-common/server/customHttpsCerts.js deleted file mode 100755 index 151f3926b22b..000000000000 --- a/packages/dbs-common/server/customHttpsCerts.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Hack to apply own certs - */ -(function () { - var https = Npm.require('https'); - var certDir = process.env.CA_CERT_PATH; - - if (!certDir) { //backwards compatible - certDir = process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'] + '/.nodeCaCerts/'; - } - - var caMap = (function () { - try { - var fs = Npm.require('fs'); - var result = {}; - if (fs.statSync(certDir).isDirectory()) { - var certList = fs.readdirSync(certDir); - for (var i = 0; i < certList.length; i++) { - result[certList[i]] = fs.readFileSync(certDir + certList[i]); - console.info('Loaded certificate ' + certList[i]); - } - } - - console.info("HTTP-Proxy", process.env.HTTP_PROXY); - console.info("HTTPS-Proxy", process.env.HTTPS_PROXY); - console.info("No Proxy", process.env.NO_PROXY); - } catch (e) { - console.warn("unable to load private root certs from path: " + certDir, e); - } - return result; - })(); - https.request = (function (request) { - return function (options, cb) { - if (options && !options.ca) { - var crt = caMap[options.hostname || options.host]; - if(crt) { - options.ca = caMap[options.hostname || options.host]; - console.info("Issuing secured request to ", (options.hostname || options.host)); - } - } - return request.call(https, options, cb); - }; - })(https.request); -})(); From e30cbef3ba4c0f4c3a5080b4d032694ea9ac03db Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 19 Feb 2017 22:53:09 +0100 Subject: [PATCH 04/83] Make knowledge-base-button re-appear --- packages/dbs-ai/client/redlink_ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dbs-ai/client/redlink_ui.js b/packages/dbs-ai/client/redlink_ui.js index 2fec9bb4a43b..6ec55e091e51 100755 --- a/packages/dbs-ai/client/redlink_ui.js +++ b/packages/dbs-ai/client/redlink_ui.js @@ -1,7 +1,7 @@ RocketChat.TabBar.removeButton('external-search'); RocketChat.TabBar.addButton({ - groups: ['livechat', 'channel'], + groups: ['live', 'channel'], id: 'external-search', i18nTitle: 'Knowledge_Base', icon: 'icon-lightbulb', From bf5697240e5021d0d095c0dc4779a601c908e005 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Mon, 20 Feb 2017 12:06:17 +0100 Subject: [PATCH 05/83] temporarily hide help request context --- packages/dbs-ai/client/views/app/tabbar/externalSearch.html | 4 ++-- packages/dbs-ai/client/views/app/tabbar/externalSearch.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.html b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html index ade0bb5f8175..1a89e3f2e7c4 100755 --- a/packages/dbs-ai/client/views/app/tabbar/externalSearch.html +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html @@ -4,12 +4,12 @@

{{_ "Knowledge_Base"}}

- {{#let helpRequest=helpRequestByRoom }} + {{#each messages}} {{#if result}}
diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js index 1b815f7cc7d0..d6c508b662b2 100755 --- a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js @@ -370,7 +370,7 @@ Template.reisebuddy_externalSearch.onCreated(function () { self.externalMessages.set(extMsg[0]); } - if(self.roomId){ + /*if(self.roomId){ self.subscribe('p2phelp:helpRequest', self.roomId); const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(self.roomId); self.helpRequest.set(helpRequest); @@ -384,6 +384,6 @@ Template.reisebuddy_externalSearch.onCreated(function () { } }); } - } + }*/ }); }); From 9d6b05bf22af203e4a9652de326ebe969f24c842 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Mon, 20 Feb 2017 13:26:29 +0100 Subject: [PATCH 06/83] Hack to initialize external messages collection --- .../server/models/LivechatExternalMessage.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/rocketchat-livechat/server/models/LivechatExternalMessage.js b/packages/rocketchat-livechat/server/models/LivechatExternalMessage.js index cd7143a3d0e1..12447bd71bfd 100644 --- a/packages/rocketchat-livechat/server/models/LivechatExternalMessage.js +++ b/packages/rocketchat-livechat/server/models/LivechatExternalMessage.js @@ -1,6 +1,12 @@ class LivechatExternalMessage extends RocketChat.models._Base { constructor() { super('livechat_external_message'); + try { + this.model = new Mongo.Collection('rocketchat_livechat_external_message'); + } + catch(e){ + console.log('I do not get why this is getting called multiple times'); + } } // FIND From bc0d39583652f1c3a810a7efad0409e94ab3f718 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Fri, 24 Feb 2017 11:48:40 +0100 Subject: [PATCH 07/83] Hack to adapt redlink results for demo Assistify --- .../dbs-ai/assets/icons/Konversationen.png | Bin 0 -> 1104 bytes packages/dbs-ai/client/redlink_ui.js | 2 +- .../views/app/tabbar/externalSearch.html | 24 +++++- .../client/views/app/tabbar/externalSearch.js | 8 +- .../views/app/tabbar/redlinkInlineResult.html | 12 +++ .../client/views/app/tabbar/redlinkQuery.html | 6 +- .../dbs-ai/i18n/externalSearch.de.i18n.yml | 1 + packages/dbs-ai/package.js | 16 ++-- packages/dbs-ai/server/config.js | 6 +- .../hooks/closeLivechatKnowledgeAdapter.js | 2 +- packages/dbs-ai/server/lib/Redlink.js | 69 +++++++++++++++++- 11 files changed, 121 insertions(+), 25 deletions(-) create mode 100755 packages/dbs-ai/assets/icons/Konversationen.png diff --git a/packages/dbs-ai/assets/icons/Konversationen.png b/packages/dbs-ai/assets/icons/Konversationen.png new file mode 100755 index 0000000000000000000000000000000000000000..5e542f3c583df5d98574307336ba23164c3235d1 GIT binary patch literal 1104 zcmV-W1h4yvP)WFU8GbZ8()Nlj2>E@cM*00Xp1L_t(o!|j%DNZfTC z$6r6QpUE;|9u^+>;3Bb!tHJL<3Zf56R*c~`us!(U0}GZ{up4317&hdJ%{|z~P#VY{ zh=r4GwkBm}WEneXKUV%ixQ_Y_Z`~~p9o!C+`z3sOaIWd*-}7t_y7!YGe81n%=lA-4 zKi@xZ$SSL>vf5TDTL}(ZmMk#O{Cqz7`2dgMa+N3#4R9_tB@>@sf5wgXDsk}K!D3ehMt{XQs(j$F?R~O?n-aOSpRdHTM%TF@w7mu!st~ZOU^D(pGlj7&7Zylz*+fJsv$m2@NVbK5WUxKZ*B!$FVv#~ z#z$AMl3TkTWOhP&hZ2B`hFv=Wxb^cGOCKX^;>P9xbIY2Px2si14CC#%ls2Ty2INfZ zc`U%u_y$!)iXsak8T%};e4sf^SRVXG&RWTLy>^lVP8B;cL`Tc}3{1v01sM+X%CV!} zm^L`-+VSmA8Hrn8^e}2?@GUlaJ>namNu8bQRxY@oQ^L_%DJ>~_AU+nJlwZfc;*)W6 z(NL(A>nAy}H}yOw!+tsb?hrHg2UfS!@2*hNb~8qoEU1|YNS0#IDYdKuA zd^KVLpR{!wtT->%5&Ao8l(b4LaYN1=^I_i2th%UozeST)yrId(tr7Y7{2*5&t1P*y z;W>L0tx!o1FyPgtJE-E&>Nxb$UTP}V2b+yf$&K&7rhn8};tf||bMB+9<5kLbD9W~2 zet@}fK>Pz=G8O%U$c(-5K+QwHXMH1^F#{QoPKr79EyDqmNMa+P4jQ}8vcE)8{u96r z`NGe=;vEd$J1C9=d~o_8rFq*};&e1M(jyly8(0Y&wcNvn6WT-J$SSL>vdZfJpnm{k W-GlaK?FV!K0000 + From 1327c0a96eb5ab602fc020bfc9f7d1f47fabe8f5 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 26 Feb 2017 13:01:49 +0100 Subject: [PATCH 15/83] Substitute "-" in template names of Hasso as it cannot be properly resolved in helpers --- .../icons/{Hasso-MLT.png => Hasso_MLT.png} | Bin .../{Hasso-Search.png => Hasso_Search.png} | Bin .../views/app/tabbar/externalSearch.html | 4 ++-- .../client/views/app/tabbar/externalSearch.js | 2 +- .../views/app/tabbar/redlinkInlineResult.html | 6 +++--- .../views/app/tabbar/redlinkInlineResult.js | 19 +++++++++++++++++- .../client/views/app/tabbar/redlinkQuery.js | 6 +++--- packages/dbs-ai/package.js | 16 +++------------ 8 files changed, 30 insertions(+), 23 deletions(-) rename packages/dbs-ai/assets/icons/{Hasso-MLT.png => Hasso_MLT.png} (100%) rename packages/dbs-ai/assets/icons/{Hasso-Search.png => Hasso_Search.png} (100%) diff --git a/packages/dbs-ai/assets/icons/Hasso-MLT.png b/packages/dbs-ai/assets/icons/Hasso_MLT.png similarity index 100% rename from packages/dbs-ai/assets/icons/Hasso-MLT.png rename to packages/dbs-ai/assets/icons/Hasso_MLT.png diff --git a/packages/dbs-ai/assets/icons/Hasso-Search.png b/packages/dbs-ai/assets/icons/Hasso_Search.png similarity index 100% rename from packages/dbs-ai/assets/icons/Hasso-Search.png rename to packages/dbs-ai/assets/icons/Hasso_Search.png diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.html b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html index 955fe24a2c7e..1f616b205793 100755 --- a/packages/dbs-ai/client/views/app/tabbar/externalSearch.html +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.html @@ -159,9 +159,9 @@

{{text queryType}}
-
+ -
+
diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js index ef42e93fd88f..b8c247372c21 100755 --- a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js @@ -109,7 +109,7 @@ Template.dbsAI_externalSearch.helpers({ const instance = Template.instance(); $(queries).each(function (indx, queryItem) { if(queries[indx].creator && typeof queries[indx].creator == "string") { - queries[indx].replacedCreator = queries[indx].creator.replace(/\./g, "_"); + queries[indx].replacedCreator = queries[indx].creator.replace(/\.|-/g, "_"); } else { queries[indx].replacedCreator = ""; } diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html index 8255c2925967..79e028ebd30e 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html @@ -74,7 +74,7 @@

- + - diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js index 815543ec2a71..90933508d719 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.js @@ -6,7 +6,7 @@ Template.redlinkQueries.helpers({ } const queriesWithInlineSupport = instance.data.queries .filter(hasInlineSupport); - + return { query: query, maxConfidence: Math.max(...queriesWithInlineSupport.map((query) => query.confidence)), @@ -14,6 +14,9 @@ Template.redlinkQueries.helpers({ templateIndex: instance.data.templateIndex, queryIndex: queryIndex } + }, + dbSearchResult(query, index){ + return query.creator == "dbsearch"; } }); diff --git a/packages/dbs-ai/package.js b/packages/dbs-ai/package.js index b9dd717de7f6..60ae0fd8ca32 100755 --- a/packages/dbs-ai/package.js +++ b/packages/dbs-ai/package.js @@ -31,6 +31,7 @@ Package.onUse(function (api) { api.addAssets('assets/icons/communication.png', 'client'); api.addAssets('assets/icons/Hasso_MLT.png', 'client'); api.addAssets('assets/icons/Hasso_Search.png', 'client'); + api.addAssets('assets/icons/dbsearch.png', 'client'); api.addFiles('server/config.js', 'server'); addDirectory(api, 'server/methods', 'server'); From 534fec1b3bce5c7bd2f7e18399e34ec35f75370c Mon Sep 17 00:00:00 2001 From: ruKurz Date: Sat, 11 Mar 2017 10:30:56 +0100 Subject: [PATCH 33/83] Added external search to the live-chat group. --- packages/assistify-help-request/client/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/assistify-help-request/client/ui.js b/packages/assistify-help-request/client/ui.js index 84faf8606430..5302a1dee5e4 100644 --- a/packages/assistify-help-request/client/ui.js +++ b/packages/assistify-help-request/client/ui.js @@ -8,7 +8,7 @@ RocketChat.TabBar.addGroup('starred-messages', ['request', 'expertise']); RocketChat.TabBar.addGroup('push-notifications', ['request', 'expertise']); RocketChat.TabBar.addButton({ - groups: ['request', 'expertise'], + groups: ['request', 'expertise', 'live'], id: 'external-search', i18nTitle: 'Knowledge_Base', icon: 'icon-lightbulb', From c46c6fc7dbad97e26b1be55a5cd872f1b2cf64b6 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 12 Mar 2017 14:09:57 +0100 Subject: [PATCH 34/83] Add feature to create groups of experts to which requests can be issued. --- .../client/lib/collections.js | 1 + .../views/sideNav/AssistifyCreateExpertise.js | 6 +-- .../views/sideNav/AssistifyCreateRequest.html | 11 +++-- .../views/sideNav/AssistifyCreateRequest.js | 44 ++++++++++++++++--- .../client/views/sideNav/expertise.html | 1 - .../client/views/sideNav/requests.html | 1 - .../{ui.js => views/tabbar/tabbarConfig.js} | 0 .../i18n/expertise.de.i18n.yml | 1 + .../i18n/request.de.i18n.yml | 1 + packages/assistify-help-request/package.js | 5 ++- .../server/methods/createRequest.js | 42 +++++++++++++++++- .../server/publications/Expertise.js | 32 +++++++++++--- .../startup/customRoomTypes.js | 4 +- packages/dbs-ai/server/lib/Redlink.js | 3 +- 14 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 packages/assistify-help-request/client/lib/collections.js rename packages/assistify-help-request/client/{ui.js => views/tabbar/tabbarConfig.js} (100%) diff --git a/packages/assistify-help-request/client/lib/collections.js b/packages/assistify-help-request/client/lib/collections.js new file mode 100644 index 000000000000..927f035d1cd6 --- /dev/null +++ b/packages/assistify-help-request/client/lib/collections.js @@ -0,0 +1 @@ +var expertiseSearchCache = new Meteor.Collection(null); diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js index 24f7aacb7b4c..ddfd6efbd4e3 100755 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js @@ -18,9 +18,9 @@ Template.AssistifyCreateExpertise.helpers({ template: Template.userSearch, noMatchTemplate: Template.userSearchEmpty, matchAll: true, - filter: { - exceptions: [Meteor.user().username].concat(Template.instance().selectedUsers.get()) - }, + // filter: { + // exceptions: [Meteor.user().username].concat(Template.instance().selectedUsers.get()) + // }, selector(match) { return { term: match }; }, diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateRequest.html b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateRequest.html index f0b3601579bf..149943b9aae6 100644 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateRequest.html +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateRequest.html @@ -1,8 +1,11 @@ diff --git a/packages/assistify-help-request/client/views/sideNav/requests.html b/packages/assistify-help-request/client/views/sideNav/requests.html index e2372a9f1545..6222222f4b46 100644 --- a/packages/assistify-help-request/client/views/sideNav/requests.html +++ b/packages/assistify-help-request/client/views/sideNav/requests.html @@ -9,5 +9,4 @@

{{_ "No_requests_yet" }}

{{/each}} - diff --git a/packages/assistify-help-request/client/ui.js b/packages/assistify-help-request/client/views/tabbar/tabbarConfig.js similarity index 100% rename from packages/assistify-help-request/client/ui.js rename to packages/assistify-help-request/client/views/tabbar/tabbarConfig.js diff --git a/packages/assistify-help-request/i18n/expertise.de.i18n.yml b/packages/assistify-help-request/i18n/expertise.de.i18n.yml index a94e43ec5cda..6385aafe0940 100644 --- a/packages/assistify-help-request/i18n/expertise.de.i18n.yml +++ b/packages/assistify-help-request/i18n/expertise.de.i18n.yml @@ -1 +1,2 @@ Expertise_needs_experts: "Bitte Experten auswählen, um eine Expertise zu definieren" +No_expertise_yet: "Noch in keiner Expertengruppe" diff --git a/packages/assistify-help-request/i18n/request.de.i18n.yml b/packages/assistify-help-request/i18n/request.de.i18n.yml index e6c808cd8a6f..d1adcf0d5c33 100644 --- a/packages/assistify-help-request/i18n/request.de.i18n.yml +++ b/packages/assistify-help-request/i18n/request.de.i18n.yml @@ -4,3 +4,4 @@ New_request: "Neue Anfrage" More_requests: "Weitere Anfragen" No_requests_yet: "Keine Anfragen bisher" Asssitify_room_count: "Letzte Nummer" +New_request_for_expertise: "Neue Anfrage zum Thema" diff --git a/packages/assistify-help-request/package.js b/packages/assistify-help-request/package.js index f6ac9bf9ea5e..237cc4a31352 100644 --- a/packages/assistify-help-request/package.js +++ b/packages/assistify-help-request/package.js @@ -61,11 +61,14 @@ Package.onUse(function (api) { api.addFiles('client/views/sideNav/expertise.html', 'client'); api.addFiles('client/views/sideNav/expertise.js', 'client'); + //Libraries + // api.addFiles('client/lib/collections.js', 'client'); + //Assets api.addAssets('assets/stylesheets/helpRequestContext.less', 'server'); //has to be done on the server, it exposes the completed css to the client //global UI modifications - api.addFiles('client/ui.js', 'client'); + api.addFiles('client/views/tabbar/tabbarConfig.js', 'client'); //i18n diff --git a/packages/assistify-help-request/server/methods/createRequest.js b/packages/assistify-help-request/server/methods/createRequest.js index d5f4cbdb6745..6f85eee61de5 100644 --- a/packages/assistify-help-request/server/methods/createRequest.js +++ b/packages/assistify-help-request/server/methods/createRequest.js @@ -1,6 +1,36 @@ Meteor.methods({ - createRequest(name, members=[]) { + createRequest(name, expertise="", members=[]) { check(name, String); + check(expertise, String); + + const getNextId = function(expertise){ + // return HelpRequestApi.getNextAssistifyRoomCode(); + const settingsRaw = RocketChat.models.Settings.model.rawCollection(); + const findAndModify = Meteor.wrapAsync(settingsRaw.findAndModify, settingsRaw); + + const query = { + _id: 'Assistify_Room_Count' + }; + + const update = { + $inc: { + value: 1 + } + }; + + const findAndModifyResult = findAndModify(query, null, update); + + return findAndModifyResult.value.value; + }; + + const getExperts = function(expertise){ + const expertiseRoom = RocketChat.models.Rooms.findOneByName(expertise); + if(expertiseRoom){ + return expertiseRoom.usernames; + } else { + return []; // even if there are no experts in the room, this is valid. A bot could notify lateron about this flaw + } + }; if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'createRequest' }); @@ -10,6 +40,16 @@ Meteor.methods({ throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'createRequest' }); } + // If an expertise has been selected, that means that a new room shall be created which addresses the + // experts of this expertise. A new room name shall be created + if(expertise && !name){ + name = expertise + '-' + getNextId(expertise); + } + + if(expertise){ + members = getExperts(expertise); + } + return RocketChat.createRoom('r', name, Meteor.user() && Meteor.user().username, members, false, {}); } }); diff --git a/packages/assistify-help-request/server/publications/Expertise.js b/packages/assistify-help-request/server/publications/Expertise.js index 0c98d21d06fc..62ec7c15b0a3 100644 --- a/packages/assistify-help-request/server/publications/Expertise.js +++ b/packages/assistify-help-request/server/publications/Expertise.js @@ -1,4 +1,4 @@ -Meteor.publish('expertise', function (limit = 50) { +Meteor.publish('autocompleteExpertise', function (selector, limit = 50) { if (typeof this.userId === 'undefined' || this.userId === null) { return this.ready(); } @@ -11,11 +11,29 @@ Meteor.publish('expertise', function (limit = 50) { return this.ready(); } - return RocketChat.models.Rooms.findByType( - 'e', - { - sort: {ts: -1}, - limit: limit + const pub = this; + const options = { + fields: { + name: 1, + t: 1 } - ) + }; + + const cursorHandle = RocketChat.models.Rooms.findByNameContainingTypesWithUsername(selector.name, [{type: 'e'}], options).observeChanges({ + added: function (_id, record) { + return pub.added('autocompleteRecords', _id, record); + }, + changed: function (_id, record) { + return pub.changed('autocompleteRecords', _id, record); + }, + removed: function (_id, record) { + return pub.removed('autocompleteRecords', _id, record); + } + }); + + this.ready(); + + this.onStop(function () { + return cursorHandle.stop(); + }); }); diff --git a/packages/assistify-help-request/startup/customRoomTypes.js b/packages/assistify-help-request/startup/customRoomTypes.js index 9446800f904f..eaace6fb91c0 100644 --- a/packages/assistify-help-request/startup/customRoomTypes.js +++ b/packages/assistify-help-request/startup/customRoomTypes.js @@ -8,7 +8,7 @@ * is asked by the one who started the request (the owner) * Expertise shall join the room (automagically) and help to get it resolved */ -RocketChat.roomTypes.add('r', 0, { +RocketChat.roomTypes.add('r', 6, { //5 is livechat template: 'requests', icon: 'icon-attention', route: { @@ -44,7 +44,7 @@ RocketChat.roomTypes.add('r', 0, { * An expert group is a private group of people who know something * An expert group is being added to a request-channel on creation based on naming conventions */ -RocketChat.roomTypes.add('e', 1, { +RocketChat.roomTypes.add('e', 15, { //20 = private messages template: 'expertise', icon: 'icon-lightbulb', route: { diff --git a/packages/dbs-ai/server/lib/Redlink.js b/packages/dbs-ai/server/lib/Redlink.js index d4d0849e2fb2..85232a19ae38 100755 --- a/packages/dbs-ai/server/lib/Redlink.js +++ b/packages/dbs-ai/server/lib/Redlink.js @@ -42,6 +42,7 @@ class RedlinkAdapter { } const room = RocketChat.models.Rooms.findOneById(rid); + const owner = room.v || room.u; //livechat or regular room RocketChat.models.Messages.find({ rid: rid, _hidden: {$ne: true}, @@ -50,7 +51,7 @@ class RedlinkAdapter { conversation.push({ content: visibleMessage.msg, time: visibleMessage.ts, - origin: (room.v._id === visibleMessage.u._id) ? 'User' : 'Agent' //in livechat, the owner of the room is the user + origin: (owner._id === visibleMessage.u._id) ? 'User' : 'Agent' //in livechat, the owner of the room is the user }); }); return conversation; From 7bb39994e4ae7dc2d59fcd95ac6b2f8ac0e96645 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 12 Mar 2017 14:10:50 +0100 Subject: [PATCH 35/83] Copy German texts from assistify to Rocket.Chat-Files since TAPi18n still ignores other yaml-files --- packages/rocketchat-i18n/i18n/de.i18n.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 077c3ef2a00a..7262442e3346 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -851,7 +851,7 @@ "Only_you_can_see_this_message": "Nur Sie können diese Nachricht sehen.", "Oops!": "Hoppla", "Open": "Öffnen", - "Open_Livechats": "Öffne Livechats", + "Open_Livechats": "Offene Livechats", "Opened": "Geöffnet", "Opens_a_channel_group_or_direct_message": "Eröffnet einen Kanal, eine Gruppe oder Direktnachrichten", "optional": "optional", @@ -1339,7 +1339,11 @@ "New_request": "Neue Anfrage", "More_requests": "Weitere Anfragen", "No_requests_yet": "Keine Anfragen bisher", - "Assistify_room_count": "Letzte Nummer" + "Assistify_room_count": "Letzte Nummer", + "New_request_for_expertise": "Neue Anfrage zum Thema" + , + "Expertise_needs_experts": "Bitte Experten auswählen, um eine Expertise zu definieren", + "No_expertise_yet": "Noch in keiner Expertengruppe" , "SystemContext": "System-Kontext", "system": "System", From 851c119418afe7a043e62cecfcef5a35b89fdb3c Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 12 Mar 2017 18:45:25 +0100 Subject: [PATCH 36/83] A tiny bit of beautification for create channel (combinedFlex) --- .../assets/stylesheets/helpRequestContext.less | 7 +++++++ .../views/sideNav/AssistifyCreateChannel.html | 3 +-- .../views/sideNav/AssistifyCreateExpertise.html | 16 +++++++++------- .../views/sideNav/AssistifyCreateExpertise.js | 6 +++--- packages/assistify/i18n/assistify.de.i18n.yml | 1 + packages/rocketchat-i18n/i18n/de.i18n.json | 3 ++- .../side-nav/createCombinedFlex.html | 2 +- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less b/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less index 667bb8765857..e42325c39073 100755 --- a/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less +++ b/packages/assistify-help-request/assets/stylesheets/helpRequestContext.less @@ -1,3 +1,10 @@ +.assistify-create-section { + margin-bottom: 30px; + + .input-submit { + margin-top: 5px; + } +} .help-request-context { padding: 15px; diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateChannel.html b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateChannel.html index 6e827c90d917..fdd79428429d 100755 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateChannel.html +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateChannel.html @@ -1,9 +1,8 @@ diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.html b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.html index 0883c9a2f70c..728258852bdf 100644 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.html +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.html @@ -7,13 +7,15 @@
{{> inputAutocomplete settings=autocompleteSettings id="experts" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} -
    - {{#each selectedUsers}} -
  • {{.}} -
  • - {{/each}} -
+ {{#if selectedUsers}} +
    + {{#each selectedUsers}} +
  • {{.}} +
  • + {{/each}} +
+ {{/if}}
diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js index ddfd6efbd4e3..294e3dccfc1e 100755 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js @@ -18,9 +18,9 @@ Template.AssistifyCreateExpertise.helpers({ template: Template.userSearch, noMatchTemplate: Template.userSearchEmpty, matchAll: true, - // filter: { - // exceptions: [Meteor.user().username].concat(Template.instance().selectedUsers.get()) - // }, + filter: { + exceptions: Template.instance().selectedUsers.get() + }, selector(match) { return { term: match }; }, diff --git a/packages/assistify/i18n/assistify.de.i18n.yml b/packages/assistify/i18n/assistify.de.i18n.yml index 811fe56161ef..8a02b9b30b17 100644 --- a/packages/assistify/i18n/assistify.de.i18n.yml +++ b/packages/assistify/i18n/assistify.de.i18n.yml @@ -1 +1,2 @@ Assistify_Show_Standard_Features: "Standard Rocket.Chat Features" +Create_new_standard_channel: "Neuen einfachen Kanal erstellen" diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 7262442e3346..b4b880b7538f 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -1329,7 +1329,8 @@ "Your_password_is_wrong": "Falsches Passwort", "Your_push_was_sent_to_s_devices": "Die Push-Nachricht wurde an %s Geräte gesendet." , - "Assistify_Show_Standard_Features": "Standard Rocket.Chat Features" + "Assistify_Show_Standard_Features": "Standard Rocket.Chat Features", + "Create_new_standard_channel": "Neuen einfachen Kanal erstellen", , "Assistify_Bot_Username": "Bot Benutzername", "Assistify_Bot_Automated_Response_Threshold": "Schwellwert für Bot-Antwort" diff --git a/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html b/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html index bba5ddf4a360..64bcefc6881a 100644 --- a/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html +++ b/packages/rocketchat-ui-sidenav/side-nav/createCombinedFlex.html @@ -8,7 +8,7 @@

{{_ "Channels"}}

{{> AssistifyCreateChannel}} {{#if showStandardFeatures}}
-

{{_ "Create_new" }}

+

{{_ "Create_new_standard_channel" }}

{{_ "Name"}} Date: Sun, 12 Mar 2017 19:25:08 +0100 Subject: [PATCH 37/83] Correct merge errors --- .../views/sideNav/AssistifyCreateExpertise.js | 28 ++-- packages/rocketchat-i18n/i18n/de.i18n.json | 2 +- .../client/createCombinedFlex.html | 127 +++++++++--------- 3 files changed, 82 insertions(+), 75 deletions(-) diff --git a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js index 294e3dccfc1e..6e47500ac3e9 100755 --- a/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js +++ b/packages/assistify-help-request/client/views/sideNav/AssistifyCreateExpertise.js @@ -1,11 +1,11 @@ import toastr from 'toastr'; Template.AssistifyCreateExpertise.helpers({ - selectedUsers: function(){ + selectedUsers: function () { const instance = Template.instance(); return instance.selectedUsers.get(); }, - autocompleteSettings: function() { + autocompleteSettings: function () { return { limit: 10, // inputDelay: 300 @@ -22,7 +22,7 @@ Template.AssistifyCreateExpertise.helpers({ exceptions: Template.instance().selectedUsers.get() }, selector(match) { - return { term: match }; + return {term: match}; }, sort: 'username' } @@ -44,16 +44,16 @@ Template.AssistifyCreateExpertise.events({ 'click .remove-expert'(e, instance) { let self = this; - let users = Template.instance().selectedUsers.get(); - users = _.reject(Template.instance().selectedUsers.get(), _id => _id === self.valueOf()); + let users = instance().selectedUsers.get(); + users = _.reject(instance().selectedUsers.get(), _id => _id === self.valueOf()); - Template.instance().selectedUsers.set(users); + instance().selectedUsers.set(users); return $('#experts').focus(); }, - 'keyup #expertise': function(e, instance) { + 'keyup #expertise': function (e, instance) { if (e.keyCode == 13) { instance.$('#experts').focus(); } @@ -66,7 +66,9 @@ Template.AssistifyCreateExpertise.events({ }, 'click .cancel-expertise': function (event, instance) { - SideNav.closeFlex(()=>{instance.clearForm()}); + SideNav.closeFlex(() => { + instance.clearForm() + }); }, 'click .save-expertise': function (event, instance) { @@ -74,7 +76,7 @@ Template.AssistifyCreateExpertise.events({ const name = instance.find('#expertise').value.toLowerCase().trim(); instance.expertiseRoomName.set(name); - if(name){ + if (name) { Meteor.call('createExpertise', name, instance.selectedUsers.get(), (err, result) => { if (err) { console.log(err); @@ -97,9 +99,11 @@ Template.AssistifyCreateExpertise.events({ } // we're done, so close side navigation and navigate to created request-channel - SideNav.closeFlex(()=>{instance.clearForm()}); - RocketChat.callbacks.run('aftercreateCombined', { _id: result.rid, name: name }); - FlowRouter.go('expertise', { name: name }, FlowRouter.current().queryParams); + SideNav.closeFlex(() => { + instance.clearForm() + }); + RocketChat.callbacks.run('aftercreateCombined', {_id: result.rid, name: name}); + FlowRouter.go('expertise', {name: name}, FlowRouter.current().queryParams); }); } else { console.log(err); diff --git a/packages/rocketchat-i18n/i18n/de.i18n.json b/packages/rocketchat-i18n/i18n/de.i18n.json index 71c35ca2a150..6fa639971fb3 100644 --- a/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/packages/rocketchat-i18n/i18n/de.i18n.json @@ -1343,7 +1343,7 @@ "Your_push_was_sent_to_s_devices": "Die Push-Nachricht wurde an %s Geräte gesendet." , "Assistify_Show_Standard_Features": "Standard Rocket.Chat Features", - "Create_new_standard_channel": "Neuen einfachen Kanal erstellen", + "Create_new_standard_channel": "Neuen einfachen Kanal erstellen" , "Assistify_Bot_Username": "Bot Benutzername", "Assistify_Bot_Automated_Response_Threshold": "Schwellwert für Bot-Antwort" diff --git a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html index 842343d3bc80..2c6eee7e5af6 100644 --- a/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html +++ b/packages/rocketchat-ui-sidenav/client/createCombinedFlex.html @@ -5,68 +5,71 @@

{{_ "Channels"}}

- {{> AssistifyCreateChannel}} - {{#if showStandardFeatures}} -
-

{{_ "Create_new_standard_channel" }} -
- {{_ "Name"}} - -
-
- {{_ "Private"}} -
- - -
-
-
- {{_ "Read_only_channel"}} -
- - -
-
-
- - {{> inputAutocomplete settings=autocompleteSettings id="channel-members" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} -
    - {{#each selectedUsers}} -
  • {{.}}
  • - {{/each}} -
-
- {{#if error.fields}} -
- {{_ "Oops!"}} - {{#each error.fields}} -

{{_ "The_field_is_required" .}}

- {{/each}} -
- {{/if}} - {{#if error.invalid}} -
- {{_ "Oops!"}} - {{{_ "Invalid_room_name" roomName}}} -
- {{/if}} - {{#if error.duplicate}} -
- {{_ "Oops!"}} - {{{_ "Duplicate_channel_name" roomName}}} -
- {{/if}} - {{#if error.archivedduplicate}} -
- {{_ "Oops!"}} - {{{_ "Duplicate_archived_channel_name" roomName}}} -
- {{/if}} -
- - -
-

+ {{> AssistifyCreateChannel}} + {{#if showStandardFeatures}} +
+

{{_ "Create_new_standard_channel" }}

+
+ {{_ "Name"}} + +
+
+ {{_ "Private"}} +
+ + +
+
+
+ {{_ "Read_only_channel"}} +
+ + +
+
+
+ + {{> inputAutocomplete settings=autocompleteSettings id="channel-members" class="search" placeholder=(_ "Search_by_username") autocomplete="off"}} +
    + {{#each selectedUsers}} +
  • {{.}} +
  • + {{/each}} +
+
+ {{#if error.fields}} +
+ {{_ "Oops!"}} + {{#each error.fields}} +

{{_ "The_field_is_required" .}}

+ {{/each}} +
+ {{/if}} + {{#if error.invalid}} +
+ {{_ "Oops!"}} + {{{_ "Invalid_room_name" roomName}}} +
+ {{/if}} + {{#if error.duplicate}} +
+ {{_ "Oops!"}} + {{{_ "Duplicate_channel_name" roomName}}} +
+ {{/if}} + {{#if error.archivedduplicate}} +
+ {{_ "Oops!"}} + {{{_ "Duplicate_archived_channel_name" roomName}}} +
+ {{/if}} +
+ + +
+
{{/if}}
From c1e2b5eab3815d181a162941a6687be11e77cbcb Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 12 Mar 2017 19:27:17 +0100 Subject: [PATCH 38/83] Fix tabbar for requests and expertise --- .../client/views/tabbar/tabbarConfig.js | 40 ++----------------- 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/packages/assistify-help-request/client/views/tabbar/tabbarConfig.js b/packages/assistify-help-request/client/views/tabbar/tabbarConfig.js index 5302a1dee5e4..0a1b5f71e1d3 100644 --- a/packages/assistify-help-request/client/views/tabbar/tabbarConfig.js +++ b/packages/assistify-help-request/client/views/tabbar/tabbarConfig.js @@ -6,6 +6,10 @@ RocketChat.TabBar.addGroup('starred-messages', ['request', 'expertise']); RocketChat.TabBar.addGroup('push-notifications', ['request', 'expertise']); +RocketChat.TabBar.addGroup('channel-settings', ['request', 'expertise']); +RocketChat.TabBar.addGroup('members-list', ['request', 'expertise']); +RocketChat.TabBar.addGroup('message-search',['request', 'expertise']); +RocketChat.TabBar.addGroup('uploaded-files-list',['request', 'expertise']); RocketChat.TabBar.addButton({ groups: ['request', 'expertise', 'live'], @@ -15,39 +19,3 @@ RocketChat.TabBar.addButton({ template: 'dbsAI_externalSearch', order: 0 }); - -RocketChat.TabBar.addButton({ - groups: ['request', 'expertise'], - id: 'channel-settings', - i18nTitle: 'Room_Info', - icon: 'icon-info-circled', - template: 'channelSettings', - order: 1 -}); - -RocketChat.TabBar.addButton({ - groups: ['request', 'expertise'], - id: 'members-list', - i18nTitle: 'Members_List', - icon: 'icon-users', - template: 'membersList', - order: 3 -}); - -RocketChat.TabBar.addButton({ - groups: ['request', 'expertise'], - id: 'message-search', - i18nTitle: 'Search', - icon: 'icon-search', - template: 'messageSearch', - order: 10 -}); - -RocketChat.TabBar.addButton({ - groups: ['request', 'expertise'], - id: 'uploaded-files-list', - i18nTitle: 'Room_uploaded_file_list', - icon: 'icon-attach', - template: 'uploadedFilesList', - order: 30 -}); From 5daa287d0e61382861059a8d3d849083228f69df Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Sun, 12 Mar 2017 20:21:47 +0100 Subject: [PATCH 39/83] Enable closing help requests opened from within rocket.chat --- .../client/views/tabbar/HelpRequestActions.js | 21 ++++++++++++------- .../views/tabbar/HelpRequestContext.html | 19 +++++++++-------- packages/assistify-help-request/server/api.js | 5 ----- .../server/methods/closeHelpRequest.js | 10 ++++----- .../server/methods/createRequest.js | 9 +++++++- .../server/models/HelpRequests.js | 2 +- .../server/methods/closeRoom.js | 10 ++++----- 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/assistify-help-request/client/views/tabbar/HelpRequestActions.js b/packages/assistify-help-request/client/views/tabbar/HelpRequestActions.js index 838a4e06b1aa..17d84fea5431 100755 --- a/packages/assistify-help-request/client/views/tabbar/HelpRequestActions.js +++ b/packages/assistify-help-request/client/views/tabbar/HelpRequestActions.js @@ -95,17 +95,22 @@ Template.HelpRequestActions.events({ return false; } - Meteor.call('assistify:closeRoom', this.roomId, inputValue, function(error) { + Meteor.call('assistify:closeHelpRequest', this.roomId, {comment: inputValue}, function(error) { if (error) { return handleError(error); + } else { + swal({ + title: t('Chat_closed'), + text: t('Chat_closed_successfully'), + type: 'success', + timer: 1000, + showConfirmButton: false + }); + + instance.helpRequest.set( + RocketChat.models.HelpRequests.findOneByRoomId(Template.currentData()) + ); } - swal({ - title: t('Chat_closed'), - text: t('Chat_closed_successfully'), - type: 'success', - timer: 1000, - showConfirmButton: false - }); }); }); } diff --git a/packages/assistify-help-request/client/views/tabbar/HelpRequestContext.html b/packages/assistify-help-request/client/views/tabbar/HelpRequestContext.html index d85e64c380e2..8fe18a0e3356 100755 --- a/packages/assistify-help-request/client/views/tabbar/HelpRequestContext.html +++ b/packages/assistify-help-request/client/views/tabbar/HelpRequestContext.html @@ -1,11 +1,12 @@ diff --git a/packages/assistify-help-request/server/api.js b/packages/assistify-help-request/server/api.js index ea6cc5b8916c..7ee737720952 100755 --- a/packages/assistify-help-request/server/api.js +++ b/packages/assistify-help-request/server/api.js @@ -124,11 +124,6 @@ class HelpRequestApi { try { Meteor.runAsUser(seekerUser._id, () => { channel = Meteor.call('createRequest', 'Assistify_' + HelpRequestApi.getNextAssistifyRoomCode(), providerUsers.map((user) => user.username)); - const room = RocketChat.models.Rooms.findOneById(channel.rid); - helpRequestId = RocketChat.models.HelpRequests.createForSupportArea(support_area, channel.rid, message, environment); - //propagate help-id to room in order to identify it as a "helped" room - RocketChat.models.Rooms.addHelpRequestInfo(room, helpRequestId); - try { if (message) { RocketChat.sendMessage({ diff --git a/packages/assistify-help-request/server/methods/closeHelpRequest.js b/packages/assistify-help-request/server/methods/closeHelpRequest.js index fb21976d3940..882e6031bf4c 100755 --- a/packages/assistify-help-request/server/methods/closeHelpRequest.js +++ b/packages/assistify-help-request/server/methods/closeHelpRequest.js @@ -1,11 +1,11 @@ Meteor.methods({ - 'assistify:closeHelpRequest'(helpRequestId, closingProps={}) { - if(helpRequestId) { - RocketChat.models.HelpRequests.close(helpRequestId, closingProps); + 'assistify:closeHelpRequest'(roomId, closingProps={}) { + const room = RocketChat.models.Rooms.findOneByIdOrName(roomId); + if(room.helpRequestId) { + RocketChat.models.HelpRequests.close(room.helpRequestId, closingProps); // delete subscriptions in order to make the room disappear from the user's clients - const helpRequest = RocketChat.models.HelpRequests.findOneById(helpRequestId); - RocketChat.models.Subscriptions.removeByRoomId(helpRequest.roomId) + RocketChat.models.Subscriptions.removeByRoomId(roomId) } } }); diff --git a/packages/assistify-help-request/server/methods/createRequest.js b/packages/assistify-help-request/server/methods/createRequest.js index 6f85eee61de5..425c538eaf83 100644 --- a/packages/assistify-help-request/server/methods/createRequest.js +++ b/packages/assistify-help-request/server/methods/createRequest.js @@ -50,6 +50,13 @@ Meteor.methods({ members = getExperts(expertise); } - return RocketChat.createRoom('r', name, Meteor.user() && Meteor.user().username, members, false, {}); + const roomCreateResult = RocketChat.createRoom('r', name, Meteor.user() && Meteor.user().username, members, false, {}); + + const room = RocketChat.models.Rooms.findOneById(roomCreateResult.rid); + const helpRequestId = RocketChat.models.HelpRequests.createForSupportArea(expertise, roomCreateResult.rid); + //propagate help-id to room in order to identify it as a "helped" room + RocketChat.models.Rooms.addHelpRequestInfo(room, helpRequestId); + + return roomCreateResult; } }); diff --git a/packages/assistify-help-request/server/models/HelpRequests.js b/packages/assistify-help-request/server/models/HelpRequests.js index f0d44f2f5cad..670ab22050f5 100755 --- a/packages/assistify-help-request/server/models/HelpRequests.js +++ b/packages/assistify-help-request/server/models/HelpRequests.js @@ -41,7 +41,7 @@ class HelpRequest extends RocketChat.models._Base { } //---------------------------- CREATE - createForSupportArea(supportArea, roomId, question, environment) { + createForSupportArea(supportArea, roomId, question="", environment={}) { const helpRequest = { createdOn: new Date(), supportArea: supportArea, diff --git a/packages/rocketchat-livechat/server/methods/closeRoom.js b/packages/rocketchat-livechat/server/methods/closeRoom.js index 6c788c24c23a..ec45f9aabdec 100644 --- a/packages/rocketchat-livechat/server/methods/closeRoom.js +++ b/packages/rocketchat-livechat/server/methods/closeRoom.js @@ -1,15 +1,15 @@ Meteor.methods({ - 'assistify:closeRoom'(roomId, comment) { - if (!Meteor.userId()) { - throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'assistify:closeRoom' }); + 'livechat:closeRoom'(roomId, comment) { + if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'close-livechat-room')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:closeRoom' }); } const room = RocketChat.models.Rooms.findOneById(roomId); const user = Meteor.user(); - if (room.usernames.indexOf(user.username) === -1) { - throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'assistify:closeRoom' }); + if (room.usernames.indexOf(user.username) === -1 && !RocketChat.authz.hasPermission(Meteor.userId(), 'close-others-livechat-room')) { + throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'livechat:closeRoom' }); } return RocketChat.Livechat.closeRoom({ From f0a7c0073c22a2c6e835fc5d71fc5f21ee7cdea9 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Mon, 13 Mar 2017 11:44:42 +0100 Subject: [PATCH 40/83] Client side results retrieval preparation --- .../dbs-ai/client/lib/ClientResultProvider.js | 31 ++++++++++++++++ .../client/views/app/tabbar/externalSearch.js | 36 +++++++++---------- .../client/views/app/tabbar/redlinkQuery.js | 16 +++++++++ packages/dbs-ai/package.js | 1 + 4 files changed, 66 insertions(+), 18 deletions(-) create mode 100644 packages/dbs-ai/client/lib/ClientResultProvider.js diff --git a/packages/dbs-ai/client/lib/ClientResultProvider.js b/packages/dbs-ai/client/lib/ClientResultProvider.js new file mode 100644 index 000000000000..955deba471e6 --- /dev/null +++ b/packages/dbs-ai/client/lib/ClientResultProvider.js @@ -0,0 +1,31 @@ +export class ClientResultFactory{ + getInstance(creator, endpointUrl){ + switch(creator){ + case 'dbsearch': return new SolrProvider(creator, endpointUrl); + } + } +} + +class SolrProvider{ + constructor(creator, endpointUrl){ + this.creator = creator; + this.endpointUrl = endpointUrl; + } + + /** + * Executes an asynchronous data retrieval and hands it over to the callback as single parameter + * @param cb + */ + executeSearch(queryParameters, cb){ + console.log("executeSearch " + this.endpointUrl); + $.ajax({ + url: this.endpointUrl, + dataType: "jsonp", + jsonp: 'json.wrf', + success: function (data) { + console.log(data); + cb(data); + } + }); + } +} diff --git a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js index 256c0f5d0b79..a61f77d01c50 100755 --- a/packages/dbs-ai/client/views/app/tabbar/externalSearch.js +++ b/packages/dbs-ai/client/views/app/tabbar/externalSearch.js @@ -361,29 +361,29 @@ Template.dbsAI_externalSearch.onCreated(function () { this.helpRequest = new ReactiveVar({}); this.roomId = null; - const self = this; + const instance = this; this.autorun(() => { - self.roomId = Template.currentData().rid; - self.subscribe('livechat:externalMessages', self.roomId); - const extMsg = RocketChat.models.LivechatExternalMessage.findByRoomId(self.roomId, {ts: -1}).fetch(); + instance.roomId = Template.currentData().rid; + instance.subscribe('livechat:externalMessages', instance.roomId); + const extMsg = RocketChat.models.LivechatExternalMessage.findByRoomId(instance.roomId, {ts: -1}).fetch(); if (extMsg.length > 0) { - self.externalMessages.set(extMsg[0]); + instance.externalMessages.set(extMsg[0]); } - if(self.roomId){ - self.subscribe('assistify:helpRequest', self.roomId); - const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(self.roomId); - self.helpRequest.set(helpRequest); + if(instance.roomId){ + instance.subscribe('assistify:helpRequest', instance.roomId); + const helpRequest = RocketChat.models.HelpRequests.findOneByRoomId(instance.roomId); + instance.helpRequest.set(helpRequest); - if(!helpRequest){ //todo remove after PoC: Non-reactive method call - Meteor.call('assistify:helpRequestByRoomId', self.roomId,(err, result) => { - if(!err){ - self.helpRequest.set(result); - } else { - console.log(err); - } - }); - } + // if(!helpRequest){ //todo remove after PoC: Non-reactive method call + // Meteor.call('assistify:helpRequestByRoomId', instance.roomId,(err, result) => { + // if(!err){ + // instance.helpRequest.set(result); + // } else { + // console.log(err); + // } + // }); + // } } }); }); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js index 50992a30934b..6f4a827b0cd9 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js @@ -1,3 +1,5 @@ +import {ClientResultFactory} from '../../../lib/ClientResultProvider.js' + Template.redlinkQuery.helpers({ hasResult(){ const results = Template.instance().state.get('results'); @@ -90,6 +92,13 @@ Template.redlinkQuery.events({ } }); +Template.redlinkQuery.clientResult = function(creator){ + switch(creator) { + case 'dbsearch': return true; + default: return false; + } +}; + Template.redlinkQuery.onCreated(function () { const instance = this; @@ -116,6 +125,13 @@ Template.redlinkQuery.onCreated(function () { instance.state.set('status', 'fetched'); }); } + if(Template.redlinkQuery.clientResult(instance.data.query.creator)){ + instance.state.set('status', 'dirty'); + ClientResultFactory.getInstance(instance.data.query.creator, instance.data.query.url) + .executeQuery([], (results)=>{ + instance.state.set('results',results); + }); + } } }) diff --git a/packages/dbs-ai/package.js b/packages/dbs-ai/package.js index 60ae0fd8ca32..cf499849efaa 100755 --- a/packages/dbs-ai/package.js +++ b/packages/dbs-ai/package.js @@ -39,6 +39,7 @@ Package.onUse(function (api) { addDirectory(api, 'server/hooks', 'server'); api.addFiles('client/redlink_ui.js', 'client'); + api.addFiles('client/lib/ClientResultProvider.js', 'client'); addDirectory(api,'client/views/app/tabbar', 'client'); //i18n From 2036217a3232bc09843cb800c690c8b6a31e8e03 Mon Sep 17 00:00:00 2001 From: "github@beimir.net" Date: Mon, 13 Mar 2017 13:32:09 +0100 Subject: [PATCH 41/83] set results ready for client-retrieved results --- packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js index 6f4a827b0cd9..f2262fc3a97a 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js @@ -130,6 +130,7 @@ Template.redlinkQuery.onCreated(function () { ClientResultFactory.getInstance(instance.data.query.creator, instance.data.query.url) .executeQuery([], (results)=>{ instance.state.set('results',results); + instance.state.set('status', 'fetched'); }); } } From 6a2b7a90cfc4e0e29a8df3d88ca2682bfe4d6c82 Mon Sep 17 00:00:00 2001 From: ruKurz Date: Mon, 13 Mar 2017 19:44:46 +0100 Subject: [PATCH 42/83] Refactored db search template. --- .../dbs-ai/client/lib/ClientResultProvider.js | 14 +++- .../views/app/tabbar/clientResultQuery.html | 37 ---------- .../views/app/tabbar/clientResultQuery.js | 68 ------------------- .../views/app/tabbar/redlinkInlineResult.html | 16 ++++- .../views/app/tabbar/redlinkInlineResult.js | 36 ++++++++-- .../views/app/tabbar/redlinkQueries.html | 4 -- .../client/views/app/tabbar/redlinkQuery.js | 17 +++-- .../app/tabbar/redlinkResultContainers.html | 2 +- .../app/tabbar/redlinkResultContainers.js | 1 + 9 files changed, 70 insertions(+), 125 deletions(-) delete mode 100644 packages/dbs-ai/client/views/app/tabbar/clientResultQuery.html delete mode 100644 packages/dbs-ai/client/views/app/tabbar/clientResultQuery.js diff --git a/packages/dbs-ai/client/lib/ClientResultProvider.js b/packages/dbs-ai/client/lib/ClientResultProvider.js index 955deba471e6..6476869d1901 100644 --- a/packages/dbs-ai/client/lib/ClientResultProvider.js +++ b/packages/dbs-ai/client/lib/ClientResultProvider.js @@ -1,5 +1,5 @@ export class ClientResultFactory{ - getInstance(creator, endpointUrl){ + getInstance (creator, endpointUrl){ switch(creator){ case 'dbsearch': return new SolrProvider(creator, endpointUrl); } @@ -23,6 +23,18 @@ class SolrProvider{ dataType: "jsonp", jsonp: 'json.wrf', success: function (data) { + + for (var j = 0; j < data.response.docs.length; j++) { + var doc = data.response.docs[j]; + var hl = data.highlighting[doc.id]; + var body = ""; + if (hl && hl['dbsearch_highlight_t_de'] && hl['dbsearch_highlight_t_de'].length > 0) { + for (var i = 0; i < hl['dbsearch_highlight_t_de'].length; i++) { + body += hl['dbsearch_highlight_t_de'][i] + " "; + } + } + doc['body'] = body; + } console.log(data); cb(data); } diff --git a/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.html b/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.html deleted file mode 100644 index 4bdfe2201832..000000000000 --- a/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.html +++ /dev/null @@ -1,37 +0,0 @@ - diff --git a/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.js b/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.js deleted file mode 100644 index 099ceeec34be..000000000000 --- a/packages/dbs-ai/client/views/app/tabbar/clientResultQuery.js +++ /dev/null @@ -1,68 +0,0 @@ -Template.clientResultQuery.onCreated(function () { - - this.helpRequest = new ReactiveVar({}); - this.url = this.data.query.url; - this.counter = 0; - this.resultsExpanded = true; - - Session.set('searchUrl', this.url + "+" + this.counter++); - - const self = this; - - this.autorun(() => { - var currUrl = Session.get('searchUrl'); - console.log("executeSearch " + currUrl); - $.ajax({ - url: currUrl, - dataType: "jsonp", - jsonp: 'json.wrf', - success: function (data) { - console.log(data); - self.helpRequest.set(data); - } - }); - }); -}); - -Template.clientResultQuery.helpers({ - - searchResult: function () { - return Template.instance().helpRequest.get(); - }, - counter: function () { - return Template.instance().counter; - }, - classExpanded(){ - const instance = Template.instance(); - return instance.resultsExpanded ? 'expanded' : 'collapsed'; - }, - dbSearchResult(query, index){ - return query.creator == "dbsearch"; - }, - getQueryDisplayTitle(){ - const instance = Template.instance(); - return instance.data.query.displayTitle; - }, - getCreatorText(){ - const instance = Template.instance(); - return instance.data.query.displayTitle; - } -}); - - -Template.clientResultQuery.events({ - - 'click .executeDBSearch': function (event, template) { - console.log("URL: " + Template.currentData().url); - console.log("Counter: " + template.counter); - Session.set('searchUrl', Template.instance().url + "+" + template.counter++); - }, - 'click .client.js-toggle-results-expanded': function (event, instance) { - const current = instance.resultsExpanded; - instance.resultsExpanded = !current; - }, - 'click .result-item-wrapper .js-toggle-result-preview-expanded': function (event, instance) { - const current = instance.resultsExpanded; - instance.resultsExpanded = !current; - } -}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html index 051a3c6348ee..f44725214481 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.html @@ -102,8 +102,8 @@
{{getResultTitle}} - {{#if link}} + {{#if link}} + {{/if}} @@ -114,7 +114,19 @@ {{> inlineResultMessage message=message}} {{/each}} +
+
+ + diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js index 83583a9a3356..e441c04d3d46 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkInlineResult.js @@ -9,7 +9,8 @@ Template.redlinkInlineResult.helpers({ const instance = Template.instance(); let templateSuffix = "generic"; - switch (instance.data.result.creator) { + const creator = instance.data.creator; + switch (instance.data.creator) { case 'bahn.de': templateSuffix = "bahn_de"; break; @@ -26,8 +27,8 @@ Template.redlinkInlineResult.helpers({ templateSuffix = "Hasso"; break; default: - if (!!Template['redlinkInlineResult_' + instance.data.result.creator]) { - templateSuffix = instance.data.result.creator; + if (!!Template['redlinkInlineResult_' + creator]) { + templateSuffix = creator; } else { templateSuffix = "generic"; } @@ -39,7 +40,8 @@ Template.redlinkInlineResult.helpers({ const instance = Template.instance(); return { result: instance.data.result, - roomId: instance.data.roomId + roomId: instance.data.roomId, + creator: instance.data.creator } } }); @@ -124,7 +126,8 @@ Template.redlinkInlineResult_Hasso.events({ 'click .result-item-wrapper .js-toggle-result-preview-expanded': function (event, instance) { const current = instance.state.get('expanded'); instance.state.set('expanded', !current); - }}); + } +}); Template.redlinkInlineResult_Hasso.helpers({ classExpanded(){ @@ -188,3 +191,26 @@ Template.redlinkInlineResult_Hasso.onCreated(function (){ }); }); + +Template.redlinkInlineResult_dbsearch.onCreated(function (){ + + this.state = new ReactiveDict(); + this.state.setDefault({ + expanded: false, + }); +}); + +Template.redlinkInlineResult_dbsearch.helpers({ + classExpanded(){ + const instance = Template.instance(); + return instance.state.get('expanded') ? 'expanded' : 'collapsed'; + } +}); + +Template.redlinkInlineResult_dbsearch.events({ + + 'click .result-item-wrapper .js-toggle-result-preview-expanded': function (event, instance) { + const current = instance.state.get('expanded'); + instance.state.set('expanded', !current); + } +}); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html index 82e02f04c2c0..4a449931a03b 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQueries.html @@ -14,10 +14,6 @@ {{> redlinkQuery (queryContext query @index)}} {{/if}} - {{#if dbSearchResult (query @index)}} - - {{> clientResultQuery (queryContext query @index)}} - {{/if}} {{/each}}
diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js index 6f4a827b0cd9..1cdc71fb5a0f 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkQuery.js @@ -39,10 +39,11 @@ Template.redlinkQuery.helpers({ const instance = Template.instance(); const results = instance.state.get('results'); if (results) { - const creator = results[0].creator; //all results have got the same creator + const creator = instance.state.get('creator'); //all results have got the same creator let options = { results: results, - roomId: instance.data.roomId + roomId: instance.data.roomId, + creator : instance.state.get('creator') }; switch (creator) { @@ -123,16 +124,18 @@ Template.redlinkQuery.onCreated(function () { Meteor.call('redlink:retrieveResults', instance.data.roomId, instance.data.templateIndex, instance.data.query.creator, (err, results)=> { instance.state.set('results', results); instance.state.set('status', 'fetched'); + instance.state.set('creator', instance.data.query.creator); }); } if(Template.redlinkQuery.clientResult(instance.data.query.creator)){ instance.state.set('status', 'dirty'); - ClientResultFactory.getInstance(instance.data.query.creator, instance.data.query.url) - .executeQuery([], (results)=>{ - instance.state.set('results',results); - }); + let crf = new ClientResultFactory().getInstance(instance.data.query.creator, instance.data.query.url); + crf.executeSearch([], (callback) => { + instance.state.set('results', callback.response.docs); + instance.state.set('status', 'fetched'); + instance.state.set('creator', instance.data.query.creator); + }); } } }) - }); diff --git a/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html index 17e0d5c13487..cd6eebea7f0e 100755 --- a/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html +++ b/packages/dbs-ai/client/views/app/tabbar/redlinkResultContainers.html @@ -1,7 +1,7 @@