BxDolAIAssistant.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaCore UNA Core
  7. * @{
  8. */
  9. define('BX_DOL_AI_ASST_TYPE_PERMANENT', 1);
  10. define('BX_DOL_AI_ASST_TYPE_TRANSIENT', 10);
  11. class BxDolAIAssistant extends BxDol
  12. {
  13. protected static $_sFileText = 'sys_asst_text.json';
  14. protected static $_sFileFaq = 'sys_asst_faq.json';
  15. protected $_oDb;
  16. protected $_iId;
  17. protected $_aData;
  18. public function __construct($aAssistant)
  19. {
  20. parent::__construct();
  21. if(empty($aAssistant) || !is_array($aAssistant))
  22. $this->_log("Unexpected value provided for the credentials");
  23. $this->_oDb = new BxDolAIQuery();
  24. $this->_iId = (int)$aAssistant['id'];
  25. $this->_aData = $aAssistant;
  26. }
  27. /**
  28. * Get assistant object instance by ID
  29. * @param $iId assistant ID
  30. * @return object instance or false on error
  31. */
  32. public static function getObjectInstance($iId)
  33. {
  34. $sPrefix = 'BxDolAIAssistant!';
  35. if(isset($GLOBALS['bxDolClasses'][$sPrefix . $iId]))
  36. return $GLOBALS['bxDolClasses'][$sPrefix . $iId];
  37. $aAssistant = BxDolAIQuery::getAssistantObject($iId);
  38. if(!$aAssistant)
  39. return false;
  40. $o = new BxDolAIAssistant($aAssistant);
  41. return ($GLOBALS['bxDolClasses'][$sPrefix . $iId] = $o);
  42. }
  43. public static function getName($sName)
  44. {
  45. return uriGenerate($sName, 'sys_agents_assistants', 'name', ['lowercase' => false]);
  46. }
  47. public static function getChatName($sName)
  48. {
  49. return uriGenerate($sName, 'sys_agents_assistants_chats', 'name', ['lowercase' => false]);
  50. }
  51. public static function pruning()
  52. {
  53. $oAi = BxDolAI::getInstance();
  54. if(getParam('sys_agents_asst_chats_trans_del') == 'on') {
  55. $aChats = $oAi->getAssistantChatsTransient(3600);
  56. foreach($aChats as $aChat)
  57. if(!empty($aChat['assistant_id']) && ($oAssistant = self::getObjectInstance($aChat['assistant_id'])) !== false)
  58. $oAssistant->deleteChat($aChat);
  59. }
  60. }
  61. public function getModelObject()
  62. {
  63. return BxDolAI::getInstance()->getModelObject($this->_aData['model_id']);
  64. }
  65. public function getChatCmtsObject($iChatId)
  66. {
  67. return BxDolAI::getInstance()->getAssistantChatCmtsObject($iChatId);
  68. }
  69. public function getAskButton($sText)
  70. {
  71. $sTitle = _t('_sys_agents_assistants_txt_ask');
  72. if(!empty($sText))
  73. $sTitle .= ': ' . strmaxtextlen($sText, 16);
  74. return BxDolTemplate::getInstance()->parseButton($sTitle, [
  75. 'class' => 'bx-btn sys-agents-ask',
  76. 'onclick' => "javascrip:bx_agents_action(this, 'asst', 'ask', {id: " . $this->_iId . ", text: '" . $sText . "'})"
  77. ]);
  78. }
  79. public function getAskChat($sName = '', $sText = '', $oTemplate = false)
  80. {
  81. if(!$oTemplate)
  82. $oTemplate = BxDolTemplate::getInstance();
  83. $bName = !empty($sName);
  84. $bText = !empty($sText);
  85. $iChatId = 0;
  86. if($bName) {
  87. $aChat = $this->_oDb->getChatsBy(['sample' => 'name', 'name' => $sName]);
  88. if(!empty($aChat) && is_array($aChat))
  89. $iChatId = (int)$aChat['id'];
  90. }
  91. if(empty($iChatId)) {
  92. if(!$bName)
  93. $sName = self::getChatName($bText ? strmaxtextlen($sText, 8) : genRndPwd());
  94. $iChatId = $this->_oDb->insertChat([
  95. 'name' => $sName,
  96. 'type' => BX_DOL_AI_ASST_TYPE_TRANSIENT,
  97. 'assistant_id' => $this->_iId,
  98. 'added' => time(),
  99. ]);
  100. }
  101. $sResult = '';
  102. if($iChatId !== false && ($oCmts = BxDolAI::getInstance()->getAssistantChatCmtsObject($iChatId, $oTemplate)) !== false) {
  103. $oCmts->setAllowDelete(false);
  104. if(!empty($sText))
  105. $oCmts->add([
  106. 'cmt_author_id' => bx_get_logged_profile_id(),
  107. 'cmt_parent_id' => 0,
  108. 'cmt_text' => $sText
  109. ]);
  110. $sResult = $oCmts->getCommentsBlock([]);
  111. }
  112. return $sResult;
  113. }
  114. public function getAskBlock($aParams = [])
  115. {
  116. return $this->getAskChat();
  117. }
  118. public function deleteChat($mixedChat)
  119. {
  120. $aChat = is_array($mixedChat) ? $mixedChat : $this->_oDb->getChatsBy(['sample' => 'id', 'id' => (int)$mixedChat]);
  121. if(empty($aChat) || !is_array($aChat))
  122. return false;
  123. if(($oCmts = $this->getChatCmtsObject($aChat['id'])) !== false)
  124. $oCmts->onObjectDelete();
  125. if(!empty($aChat['ai_file_id'])) {
  126. $oAIModel = $this->getModelObject();
  127. $oAIModel->callVectorStoresFilesDelete($this->_aData['ai_vs_id'], $aChat['ai_file_id']);
  128. $oAIModel->callFilesDelete($aChat['ai_file_id']);
  129. }
  130. return $this->_oDb->deleteChats(['id' => $aChat['id']]);
  131. }
  132. public function processActionAddKnowledge()
  133. {
  134. $sAction = 'add_knowledge';
  135. $oTemplate = BxDolTemplate::getInstance();
  136. $aForm = $this->_getForm($sAction, ['id' => $this->_iId]);
  137. $oForm = new BxTemplFormView($aForm);
  138. $oForm->initChecker();
  139. if($oForm->isSubmittedAndValid()) {
  140. $oAIModel = $this->getModelObject();
  141. $sType = $oForm->getCleanValue('type');
  142. $sFile = self::${'_sFile' . ucfirst($sType)};
  143. $aFile = $this->_oDb->getFilesBy(['sample' => 'assistant_id', 'assistant_id' => $this->_iId, 'name' => $sFile]);
  144. $aContent = [];
  145. if(!empty($aFile) && is_array($aFile) && !empty($aFile['ai_file_id'])) {
  146. $sContent = $oAIModel->callFilesRetrieveContent($aFile['ai_file_id']);
  147. $aContent = json_decode($sContent, true);
  148. if(!$oAIModel->callVectorStoresFilesDelete($this->_aData['ai_vs_id'], $aFile['ai_file_id']))
  149. return echoJson([]);
  150. if(!$oAIModel->callFilesDelete($aFile['ai_file_id']))
  151. return echoJson([]);
  152. $this->_oDb->deleteFiles(['id' => $aFile['id']]);
  153. }
  154. switch($sType) {
  155. case 'text':
  156. $aContent[] = bx_process_input($oForm->getCleanValue('text'));
  157. break;
  158. case 'faq':
  159. $aContent[] = [
  160. 'question' => bx_process_input($oForm->getCleanValue('faq_q')),
  161. 'answer' => bx_process_input($oForm->getCleanValue('faq_a'))
  162. ];
  163. break;
  164. }
  165. $aFileResponse = $oAIModel->callFiles(['content' => json_encode($aContent), 'name' => $sFile, 'mime' => 'application/json']);
  166. if(!$aFileResponse)
  167. return echoJson([]);
  168. if(!$oAIModel->callVectorStoresFiles($this->_aData['ai_vs_id'], ['file_id' => $aFileResponse['id']]))
  169. return echoJson([]);
  170. $this->_oDb->insertFile([
  171. 'name' => $sFile,
  172. 'assistant_id' => $this->_iId,
  173. 'added' => time(),
  174. 'ai_file_id' => $aFileResponse['id'],
  175. 'ai_file_size' => $aFileResponse['bytes']
  176. ]);
  177. return echoJson(['msg' => _t('_sys_agents_assistants_msg_knowledge_added')]);
  178. }
  179. $sFormId = $oForm->getId();
  180. $sContent = BxTemplFunctions::getInstance()->popupBox($sFormId . '_popup', _t('_sys_agents_assistants_popup_add_knowledge'), $oTemplate->parseHtmlByName('agents_form.html', [
  181. 'form_id' => $sFormId,
  182. 'form' => $oForm->getCode(true),
  183. ]));
  184. return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
  185. }
  186. public function processActionAsk()
  187. {
  188. $sAction = 'ask';
  189. $oTemplate = BxDolTemplate::getInstance();
  190. $sText = bx_get('text');
  191. if($sText !== false)
  192. $sText = bx_process_input($sText);
  193. $iChatId = $this->_oDb->insertChat([
  194. 'name' => self::getChatName(strmaxtextlen($sText, 8)),
  195. 'type' => BX_DOL_AI_ASST_TYPE_TRANSIENT,
  196. 'assistant_id' => $this->_iId,
  197. 'added' => time(),
  198. ]);
  199. $sPopupContent = '';
  200. if($iChatId !== false && ($oCmts = BxDolAI::getInstance()->getAssistantChatCmtsObject($iChatId, $oTemplate)) !== false) {
  201. $oCmts->setAllowDelete(false);
  202. $oCmts->add([
  203. 'cmt_author_id' => bx_get_logged_profile_id(),
  204. 'cmt_parent_id' => 0,
  205. 'cmt_text' => $sText
  206. ]);
  207. $sPopupContent = $oCmts->getCommentsBlock([], ['dynamic_mode' => true]);
  208. }
  209. $sPopupId = 'bx_agents_assistants_ask';
  210. $sPopupContent = BxTemplFunctions::getInstance()->popupBox($sPopupId, _t('_sys_agents_assistants_popup_ask'), $oTemplate->parseHtmlByName('agents_popup.html', [
  211. 'content' => $sPopupContent
  212. ]));
  213. return echoJson(['popup' => ['html' => $sPopupContent, 'options' => ['closeOnOuterClick' => false]]]);
  214. }
  215. protected function _getForm($sAction, $aAssistant = [])
  216. {
  217. $aForm = [
  218. 'form_attrs' => [
  219. 'id' => 'bx_agents_assistants_' . $sAction,
  220. 'action' => BX_DOL_URL_ROOT . bx_append_url_params('agents.php', ['t' => 'asst', 'a' => $sAction]),
  221. 'method' => 'post',
  222. ],
  223. 'params' => [
  224. 'db' => [
  225. 'submit_name' => 'do_submit',
  226. ],
  227. ],
  228. 'inputs' => [],
  229. ];
  230. switch($sAction) {
  231. case 'add_knowledge';
  232. $aForm['inputs'] = [
  233. 'id' => [
  234. 'type' => 'hidden',
  235. 'name' => 'id',
  236. 'value' => $aAssistant['id'],
  237. ],
  238. 'type' => [
  239. 'type' => 'select',
  240. 'name' => 'type',
  241. 'caption' => _t('_sys_agents_assistants_field_kwg_type'),
  242. 'value' => '',
  243. 'values' => [
  244. ['key' => 'text', 'value' => _t('_sys_agents_assistants_field_kwg_type_text')],
  245. ['key' => 'faq', 'value' => _t('_sys_agents_assistants_field_kwg_type_faq')]
  246. ],
  247. 'attrs' => ['onchange' => "javascript:bx_aa_ak_type_change(this)"],
  248. ],
  249. 'text' => [
  250. 'type' => 'textarea',
  251. 'name' => 'text',
  252. 'caption' => _t('_sys_agents_assistants_field_kwg_text'),
  253. 'value' => '',
  254. 'tr_attrs' => ['df' => '1', 'dt' => 'text']
  255. ],
  256. 'faq_q' => [
  257. 'type' => 'text',
  258. 'name' => 'faq_q',
  259. 'caption' => _t('_sys_agents_assistants_field_kwg_faq_q'),
  260. 'value' => '',
  261. 'tr_attrs' => ['df' => '1', 'dt' => 'faq', 'style' => 'display:none']
  262. ],
  263. 'faq_a' => [
  264. 'type' => 'textarea',
  265. 'name' => 'faq_a',
  266. 'caption' => _t('_sys_agents_assistants_field_kwg_faq_a'),
  267. 'value' => '',
  268. 'tr_attrs' => ['df' => '1', 'dt' => 'faq', 'style' => 'display:none']
  269. ],
  270. 'submit' => [
  271. 'type' => 'input_set',
  272. 0 => [
  273. 'type' => 'submit',
  274. 'name' => 'do_submit',
  275. 'value' => _t('_sys_submit'),
  276. ],
  277. 1 => [
  278. 'type' => 'reset',
  279. 'name' => 'close',
  280. 'value' => _t('_sys_close'),
  281. 'attrs' => ['class' => 'bx-def-margin-sec-left', 'onclick' => '$(\'.bx-popup-applied:visible\').dolPopupHide();'],
  282. ],
  283. ],
  284. ];
  285. break;
  286. }
  287. return $aForm;
  288. }
  289. }