Aleksandr Trofimov пре 7 месеци
родитељ
комит
b3110b17e9

+ 49 - 0
inc/classes/BxDolAI.php

@@ -80,6 +80,31 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         return self::getInstance()->getAiProviderInstance($iId);
     }   
 
+    public static function getAgentInstance(int $iId): NeuronAI\Agent\Agent
+    {
+        if (isset($GLOBALS['bxDolClasses'][__CLASS__ . '_Agent_' . $iId]))
+            return $GLOBALS['bxDolClasses'][__CLASS__ . '_Agent_' . $iId];
+
+        $a = BxDolAIQuery::getAgentObject($iId);
+        if (!$a || !$a['active'] || !$a['prompt_system'] || !$a['model_id']) {
+            return null;
+        }
+
+        $o = BxDolAiAgent::make($a);
+
+        if ($a['vector_store_id']) {
+            $aVectorStore = BxDolAIQuery::getVectorStoreObject($a['vector_store_id']);
+            if ($aVectorStore && $aVectorStore['embedding_provider_id']) {
+                $oEmbedder = self::getAiEmbeddingsProviderInstance($aVectorStore['embedding_provider_id']);
+                $o->setEmbeddingsProvider($oEmbedder);
+            }
+        }
+
+        $GLOBALS['bxDolClasses'][__CLASS__ . '_Agent_' . $iId] = $o;
+
+        return $o;
+    }
+
     public static function getModelInstance(int $iId): NeuronAI\Providers\AIProviderInterface | NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface
     {
         if (isset($GLOBALS['bxDolClasses'][__CLASS__ . '_Model_' . $iId]))
@@ -623,6 +648,30 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         return $aAutomators;
     }
 
+    public function callAgent($sType, $aAgent, $aParams = [])
+    {
+        // update sample data
+        $a = ['alert' => 'alert_sample', 'webhook' => 'webhook_sample'];
+        if (isset($a[$sType]) && empty($aAgent[$a[$sType]])) {
+            $this->updateAgentField($aAgent['id'], $a[$sType], json_encode($aParams));
+        }
+
+        $o = self::getAgentInstance($aAgent['id']);
+        if (!$o)
+            return false;
+
+        $o->chat(new UserMessage($aParams));
+
+        $o->run();
+
+        return $o->getMessage();
+    }
+
+    public function getAgentsByAlertUnitAndAction($sUnit, $sAction)
+    {
+        return $this->_oDb->getAgentsByAlertUnitAndAction($sUnit, $sAction);
+    }
+
     public function callAutomator($sType, $aParams = [])
     {
         $sMethod = '_callAutomator' . bx_gen_method_name($sType);

+ 40 - 4
inc/classes/BxDolAIQuery.php

@@ -36,6 +36,17 @@ class BxDolAIQuery extends BxDolDb
         return $aVectorStore;
     }
 
+    static public function getAgentObject($iId)
+    {
+        $oDb = BxDolDb::getInstance();
+
+        $a = $oDb->getRow("SELECT * FROM `sys_agents_agents` WHERE `id` = :id", ['id' => $iId]);
+        if(!$a || !is_array($a))
+            return false;
+
+        return $a;
+    }
+
     static public function getProviderObject($iId)
     {
         $oDb = BxDolDb::getInstance();
@@ -120,11 +131,15 @@ class BxDolAIQuery extends BxDolDb
                 }
 
                 if(isset($aParams['capabilities'])) {
-                    $aMethod['params'][3]['capabilities'] = $aParams['capabilities'];
-
-                    $sWhereClause .= " AND `capabilities` = :capabilities";
+                    if (is_array($aParams['capabilities']))
+                    {                        
+                        $sWhereClause .= " AND `capabilities` IN (" . $this->implode_escape($aParams['capabilities']) . ")"; 
+                    }
+                    else {
+                        $aMethod['params'][3]['capabilities'] = $aParams['capabilities'];
+                        $sWhereClause .= " AND `capabilities` = :capabilities";
+                    }
                 }
-
                 break;
         }
 
@@ -809,6 +824,12 @@ class BxDolAIQuery extends BxDolDb
         return (int)$this->query("DELETE FROM `sys_agents_assistants_files` WHERE " . $this->arrayToSQL($aParamsWhere, ' AND ')) > 0;
     }
 
+    public function getVectorStores (): mixed
+    {
+        $sQuery = "SELECT `id`, `title` FROM `sys_agents_vector_store` WHERE `active` = 1 ORDER BY `title` ASC";
+        return $this->getPairs($sQuery, 'id', 'title'); 
+    }
+
     public function getVectorStoreById (int $iId): mixed
     {
         $sQuery = "SELECT * FROM `sys_agents_vector_store` WHERE `id` = :id";
@@ -871,6 +892,21 @@ class BxDolAIQuery extends BxDolDb
         $sQuery = "UPDATE `sys_agents_vector_store_data` SET `status` = :status WHERE `id` = :id";
         return $oDb->query($sQuery, ['status' => $sStatus, 'id' => $iId]);
     }
+
+    public function getTools()
+    {
+        return [];
+    }
+
+    public function getAgentsByAlertUnitAndAction($sUnit, $sAction)
+    {
+        return $this->getAll("SELECT * FROM `sys_agents_agents` WHERE `alert_unit` = :unit AND `alert_action` = :action", ['unit' => $sUnit, 'action' => $sAction]);
+    }
+
+    public function updateAgentField($iId, $sField, $sValue)
+    {
+        return $this->query("UPDATE `sys_agents_agents` SET `$sField` = :value WHERE `id` = :id", ['value' => $sValue, 'id' => $iId]);
+    }
 }
 
 /** @} */

+ 49 - 0
inc/classes/BxDolAiAgent.php

@@ -0,0 +1,49 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaCore UNA Core
+ * @{
+ */
+
+use NeuronAI\RAG\RAG;
+
+class BxDolAiAgent extends RAG
+{
+    public function __construct(protected array $aAgent)
+    {}
+
+    protected function provider(): AIProviderInterface
+    {
+        return BxDolAi::getModelInstance($this->aAgent['model_id']);
+    }
+
+    protected function instructions(): string
+    {
+        $oPrompt = new NeuronAI\Agent\SystemPrompt(
+            background: [$aAgent['prompt_system']],
+            steps: !empty($aAgent['prompt_steps']) ? [$aAgent['prompt_steps']] : [],
+            output: !empty($aAgent['prompt_output']) ? [$aAgent['prompt_output']] : [],
+            toolsUsage: !empty($aAgent['prompt_tools']) ? [$aAgent['prompt_tools']] : []
+        );
+        return (string) $oPrompt;
+    }
+
+    /**
+     * @return \NeuronAI\Tools\ToolInterface[]
+     */
+    protected function tools(): array
+    {
+        // TODO:
+        return [];
+    }
+    
+    protected function vectorStore(): VectorStoreInterface
+    {
+        if ($this->aAgent['vector_store_id'])
+            return BxDolAi::getVectorStoreInstance($this->aAgent['vector_store_id']);
+        else
+            return new NeuronAI\RAG\VectorStore\MemoryVectorStore();
+    }
+}

+ 16 - 1
inc/classes/BxDolAlerts.php

@@ -144,6 +144,21 @@ class BxDolAlerts extends BxDol
             }
         }
 
+        // call agents
+        $oAi = BxDolAI::getInstance();
+        if($aAgents = $oAi->getAgentsByAlertUnitAndAction($this->sUnit, $this->sAction)) {
+            foreach($aAgents as $a) {
+                $oAi->callAgent('alert', $a, [
+                    'trigger' => 'alert',
+                    'object' => $this->iObject,
+                    'sender' => $this->iSender,
+                    'unit' => $this->sUnit, 
+                    'action' => $this->sAction,
+                    'extra' => $this->aExtras
+                ]);
+            }
+        }
+
         // call automators
         $oAi = BxDolAI::getInstance();
         if($oAi->hasAutomators(BX_DOL_AI_AUTOMATOR_EVENT, true)) {
@@ -154,7 +169,7 @@ class BxDolAlerts extends BxDol
                     'alert' => $this
                 ]);
             }
-        }
+        }        
     }
 
     /**

+ 20 - 0
install/sql/system.sql

@@ -5810,6 +5810,26 @@ INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable
 INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `active`, `order`) VALUES
 ('sys_studio_agents_vector_store_data', 'bulk', 'delete', '_Delete', '', 0, 1, 1, 10);
 
+-- GRID: Agents
+INSERT INTO `sys_objects_grid` (`object`, `source_type`, `source`, `table`, `field_id`, `field_order`, `field_active`, `paginate_url`, `paginate_per_page`, `paginate_simple`, `paginate_get_start`, `paginate_get_per_page`, `filter_fields`, `filter_fields_translatable`, `filter_mode`, `sorting_fields`, `sorting_fields_translatable`, `override_class_name`, `override_class_file`) VALUES
+('sys_studio_agents_agents', 'Sql', 'SELECT * FROM `sys_agents_agents` WHERE 1 ', 'sys_agents_agents', 'id', 'added', 'active', '', 20, NULL, 'start', '', 'name', '', 'like', '', '', 'BxTemplStudioAgentsAgents', '');
+
+INSERT INTO `sys_grid_fields` (`object`, `name`, `title`, `width`, `translatable`, `chars_limit`, `params`, `hidden_on`, `order`) VALUES
+('sys_studio_agents_agents', 'checkbox', '', '2%', 0, 0, '', '', 1),
+('sys_studio_agents_agents', 'switcher', '_sys_active', '8%', 0, 0, '', '', 2),
+('sys_studio_agents_agents', 'name', '_sys_agents_agents_txt_name', '10%', 0, 0, '', '', 3),
+('sys_studio_agents_agents', 'trigger', '_sys_agents_agents_txt_trigger', '8%', 0, 0, '', '', 4),
+('sys_studio_agents_agents', 'profile_id', '_sys_agents_agents_txt_profile_id', '10%', 0, 0, '', '', 6),
+('sys_studio_agents_agents', 'actions', '', '20%', 0, 0, '', '', 11);
+
+INSERT INTO `sys_grid_actions` (`object`, `type`, `name`, `title`, `icon`, `icon_only`, `confirm`, `order`) VALUES
+('sys_studio_agents_agents', 'bulk', 'delete', '_Delete', '', 0, 1, 1),
+('sys_studio_agents_agents', 'single', 'manual', '_Run', 'play', 1, 0, 1),
+('sys_studio_agents_agents', 'single', 'edit', '_Edit', 'pencil-alt', 1, 0, 2),
+('sys_studio_agents_agents', 'single', 'delete', '_Delete', 'remove', 1, 1, 3),
+('sys_studio_agents_agents', 'independent', 'add', '_adm_form_btn_field_add', '', 0, 0, 1);
+
+
 -- --------------------------------------------------------
 
 

+ 38 - 3
modules/boonex/english/data/langs/system/en.xml

@@ -2257,22 +2257,57 @@
     <string name="_sys_agents_automators_txt_code"><![CDATA[Code]]></string>
     <string name="_sys_agents_automators_txt_added"><![CDATA[Added]]></string>
     <string name="_sys_agents_automators_txt_status"><![CDATA[Status]]></string>
+    
+    <string name="_sys_agents_agents_txt_name"><![CDATA[Name]]></string>
+    <string name="_sys_agents_agents_txt_trigger"><![CDATA[Trigger]]></string>
+    <string name="_sys_agents_agents_txt_profile_id"><![CDATA[Profile]]></string>
+    <string name="_sys_agents_agents_popup_add"><![CDATA[Add agent]]></string>
+    
+    <string name="_Run"><![CDATA[Run]]></string>
+    
     <string name="_sys_agents_automators_btn_add"><![CDATA[Add]]></string>
     <string name="_sys_agents_automators_btn_tune"><![CDATA[Tune]]></string>
     <string name="_sys_agents_automators_btn_approve"><![CDATA[Approve]]></string>
     <string name="_sys_agents_automators_popup_add"><![CDATA[Add automator]]></string>
     <string name="_sys_agents_automators_popup_edit"><![CDATA[Edit automator]]></string>
     <string name="_sys_agents_automators_field_name"><![CDATA[Name]]></string>
+    <string name="_sys_agents_automators_field_name_info"><![CDATA[Uniq agent name]]></string>
     <string name="_sys_agents_automators_field_model_id"><![CDATA[Model]]></string>
+    <string name="_sys_agents_automators_field_model_id_info"><![CDATA[AI model to use for this agent.]]></string>
     <string name="_sys_agents_automators_field_profile_id"><![CDATA[Profile]]></string>
     <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Leave empty to use default.]]></string>
+    <string name="_sys_agents_field_profile_id"><![CDATA[Profile]]></string>
+    <string name="_sys_agents_field_profile_id_inf"><![CDATA[AI agent will perform actions under this profile]]></string>
     <string name="_sys_agents_automators_field_type"><![CDATA[Type]]></string>
     <string name="_sys_agents_automators_field_type_event"><![CDATA[Event]]></string>
     <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Scheduler]]></string>
     <string name="_sys_agents_automators_field_type_webhook"><![CDATA[Webhook]]></string>
-    <string name="_sys_agents_automators_field_alert_unit"><![CDATA[Event Type]]></string>
-    <string name="_sys_agents_automators_field_alert_action"><![CDATA[Event Action]]></string>
-    <string name="_sys_agents_automators_field_scheduler_time"><![CDATA[Scheduler Time]]></string>
+    <string name="_sys_agents_automators_field_trigger"><![CDATA[Trigger]]></string>
+    <string name="_sys_agents_automators_field_trigger_info"><![CDATA[Trigger which will initiate agent to run. Alert — system event notification. Scheduler — time-based scheduled execution. Webhook — external HTTP trigger. Manual — user-initiated run. Agent — triggered by another agent. Message — triggered by chat message.]]></string>
+    <string name="_sys_agents_field_alert_sample"><![CDATA[Alert: sample data]]></string>
+    <string name="_sys_agents_waiting_for_sample_data"><![CDATA[Waiting for sample data...]]></string>
+    <string name="_sys_agents_field_alert_unit"><![CDATA[Alert: event type]]></string>
+    <string name="_sys_agents_field_alert_unit_info"><![CDATA[Type of event that triggers the agent, usually 'system' for system events, 'module_name' for modules events.]]></string>
+    <string name="_sys_agents_field_alert_action"><![CDATA[Alert: event action]]></string>
+    <string name="_sys_agents_automators_field_scheduler_cron"><![CDATA[Scheduler (CRON)]]></string>
+    <string name="_sys_agents_automators_field_scheduler_cron_info"><![CDATA[CRON expression for scheduling the agent.]]></string>
+    <string name="_sys_agents_automators_field_webhook_key"><![CDATA[Webhook: key]]></string>
+    <string name="_sys_agents_automators_field_webhook_key_info"><![CDATA[Unique key for webhook endpoint, used as Bearer Authentication header.]]></string>
+    <string name="_sys_agents_field_webhook_sample"><![CDATA[Webhook: sample payload]]></string>
+    <string name="_sys_agents_automators_field_message_profile"><![CDATA[Message: profile for communication with agent]]></string>
+    <string name="_sys_agents_automators_field_message_profile_info"><![CDATA[Profile to use for sending messages to the agent, messages form other users are ignored.]]></string>
+    <string name="_sys_agents_automators_field_vector_store_id"><![CDATA[Knowledge]]></string>
+    <string name="_sys_agents_automators_field_vector_store_id_info"><![CDATA[Vector store for knowledge retrieval.]]></string>
+    <string name="_sys_agents_automators_field_tools"><![CDATA[Tools]]></string>
+    <string name="_sys_agents_automators_field_tools_info"><![CDATA[Tools available for the agent to use.]]></string>
+    <string name="_sys_agents_automators_field_prompt_system"><![CDATA[System Prompt]]></string>
+    <string name="_sys_agents_automators_field_prompt_system_info"><![CDATA[System prompt for the agent.]]></string>
+    <string name="_sys_agents_automators_field_prompt_steps"><![CDATA[Steps Prompt]]></string>
+    <string name="_sys_agents_automators_field_prompt_steps_info"><![CDATA[Instructions for executing agent steps.]]></string>
+    <string name="_sys_agents_automators_field_prompt_output"><![CDATA[Output Prompt]]></string>
+    <string name="_sys_agents_automators_field_prompt_output_info"><![CDATA[Instructions for formatting the output.]]></string>
+    <string name="_sys_agents_automators_field_prompt_tools"><![CDATA[Tools Prompt]]></string>
+    <string name="_sys_agents_automators_field_prompt_tools_info"><![CDATA[Instructions for using available tools.]]></string>
     <string name="_sys_agents_automators_field_providers"><![CDATA[Providers]]></string>
     <string name="_sys_agents_automators_field_providers_add"><![CDATA[Add]]></string>
     <string name="_sys_agents_automators_field_helpers"><![CDATA[Helpers]]></string>

+ 38 - 3
modules/boonex/russian/data/langs/system/ru.xml

@@ -2232,22 +2232,57 @@
         <string name="_sys_agents_automators_txt_code"><![CDATA[Код]]></string>
         <string name="_sys_agents_automators_txt_added"><![CDATA[Добавлено]]></string>
         <string name="_sys_agents_automators_txt_status"><![CDATA[Статус]]></string>
+        
+        <string name="_sys_agents_agents_txt_name"><![CDATA[Название]]></string>
+        <string name="_sys_agents_agents_txt_trigger"><![CDATA[Триггер]]></string>
+        <string name="_sys_agents_agents_txt_profile_id"><![CDATA[Профиль]]></string>
+        <string name="_sys_agents_agents_popup_add"><![CDATA[Добавить агента]]></string>
+        
+        <string name="_Run"><![CDATA[Запустить]]></string>
+        
         <string name="_sys_agents_automators_btn_add"><![CDATA[Добавить]]></string>
         <string name="_sys_agents_automators_btn_tune"><![CDATA[Настройка]]></string>
         <string name="_sys_agents_automators_btn_approve"><![CDATA[Одобрить]]></string>
         <string name="_sys_agents_automators_popup_add"><![CDATA[Добавить автоматизатор]]></string>
         <string name="_sys_agents_automators_popup_edit"><![CDATA[Редактировать автоматизатор]]></string>
         <string name="_sys_agents_automators_field_name"><![CDATA[Название]]></string>
+        <string name="_sys_agents_automators_field_name_info"><![CDATA[Уникальное название агента.]]></string>
         <string name="_sys_agents_automators_field_model_id"><![CDATA[Модель]]></string>
+        <string name="_sys_agents_automators_field_model_id_info"><![CDATA[Модель тьи для использования этим агентом.]]></string>
         <string name="_sys_agents_automators_field_profile_id"><![CDATA[Профиль]]></string>
         <string name="_sys_agents_automators_field_profile_id_inf"><![CDATA[Оставьте пустым, чтобы использовать значение по умолчанию.]]></string>
+        <string name="_sys_agents_field_profile_id"><![CDATA[Профиль]]></string>
+        <string name="_sys_agents_field_profile_id_inf"><![CDATA[Профайл, от имени которого ИИ агент выполняет действия.]]></string>
         <string name="_sys_agents_automators_field_type"><![CDATA[Тип]]></string>
         <string name="_sys_agents_automators_field_type_event"><![CDATA[Событие]]></string>
         <string name="_sys_agents_automators_field_type_scheduler"><![CDATA[Планировщик]]></string>
         <string name="_sys_agents_automators_field_type_webhook"><![CDATA[Вебхук]]></string>
-        <string name="_sys_agents_automators_field_alert_unit"><![CDATA[Тип события]]></string>
-        <string name="_sys_agents_automators_field_alert_action"><![CDATA[Действие события]]></string>
-        <string name="_sys_agents_automators_field_scheduler_time"><![CDATA[Время для планировщика]]></string>
+        <string name="_sys_agents_automators_field_trigger"><![CDATA[Триггер]]></string>
+        <string name="_sys_agents_automators_field_trigger_info"><![CDATA[Триггер, который запускает агента. Alert — системное событие. Scheduler — запуск по расписанию. Webhook — HTTP внешний вызов. Manual — ручной запуск. Agent — запуск другим агентом. Message — сообщение пользователя.]]></string>
+        <string name="_sys_agents_field_alert_sample"><![CDATA[Alert - пример данных]]></string>
+        <string name="_sys_agents_waiting_for_sample_data"><![CDATA[Ожидание примера данных...]]></string>
+        <string name="_sys_agents_field_alert_unit"><![CDATA[Alert - тип события]]></string>
+        <string name="_sys_agents_field_alert_unit_info"><![CDATA[Тип события, которое вызывает агента, обычно 'system' - для системных событий, 'имя_модуля' - для модульных событий]]></string>
+        <string name="_sys_agents_field_alert_action"><![CDATA[Действие события]]></string>
+        <string name="_sys_agents_automators_field_scheduler_cron"><![CDATA[Планировщик (CRON)]]></string>
+        <string name="_sys_agents_automators_field_scheduler_cron_info"><![CDATA[Выражение CRON для планирования агента.]]></string>
+        <string name="_sys_agents_automators_field_webhook_key"><![CDATA[Вебхук: ключ]]></string>
+        <string name="_sys_agents_automators_field_webhook_key_info"><![CDATA[Уникальный секретный ключ для вебхука, используется для заголовка Bearer Authentication]]></string>
+        <string name="_sys_agents_field_webhook_sample"><![CDATA[Вебхук: пример данных]]></string>
+        <string name="_sys_agents_automators_field_message_profile"><![CDATA[Профиль сообщения]]></string>
+        <string name="_sys_agents_automators_field_message_profile_info"><![CDATA[Профиль для использования при отправке сообщений.]]></string>
+        <string name="_sys_agents_automators_field_vector_store_id"><![CDATA[Хранилище векторов]]></string>
+        <string name="_sys_agents_automators_field_vector_store_id_info"><![CDATA[Хранилище векторов для получения знаний.]]></string>
+        <string name="_sys_agents_automators_field_tools"><![CDATA[Инструменты]]></string>
+        <string name="_sys_agents_automators_field_tools_info"><![CDATA[Инструменты, доступные для использования агентом.]]></string>
+        <string name="_sys_agents_automators_field_prompt_system"><![CDATA[Системная инструкция]]></string>
+        <string name="_sys_agents_automators_field_prompt_system_info"><![CDATA[Системная инструкция для агента.]]></string>
+        <string name="_sys_agents_automators_field_prompt_steps"><![CDATA[Инструкция по шагам]]></string>
+        <string name="_sys_agents_automators_field_prompt_steps_info"><![CDATA[Инструкции по выполнению шагов агента.]]></string>
+        <string name="_sys_agents_automators_field_prompt_output"><![CDATA[Инструкция по выходу]]></string>
+        <string name="_sys_agents_automators_field_prompt_output_info"><![CDATA[Инструкции по форматированию результата.]]></string>
+        <string name="_sys_agents_automators_field_prompt_tools"><![CDATA[Инструкция по инструментам]]></string>
+        <string name="_sys_agents_automators_field_prompt_tools_info"><![CDATA[Инструкции по использованию доступных инструментов.]]></string>
         <string name="_sys_agents_automators_field_providers"><![CDATA[Поставщики]]></string>
         <string name="_sys_agents_automators_field_providers_add"><![CDATA[Добавить]]></string>
         <string name="_sys_agents_automators_field_helpers"><![CDATA[Помощники]]></string>

+ 1 - 1
studio/classes/BxDolStudioAgents.php

@@ -14,7 +14,7 @@ define('BX_DOL_STUDIO_AGENTS_TYPE_AI_PROVIDERS', 'ai_providers');
 define('BX_DOL_STUDIO_AGENTS_TYPE_TOOLS', 'tools');
 define('BX_DOL_STUDIO_AGENTS_TYPE_EMBEDDING_PROVIDERS', 'embedding_providers');
 define('BX_DOL_STUDIO_AGENTS_TYPE_VECTOR_STORE', 'vector_store');
-
+define('BX_DOL_STUDIO_AGENTS_TYPE_AGENTS', 'agents');
 
 /*
  * Isn't used for now. Most probably they will be removed.

+ 28 - 0
studio/classes/BxDolStudioAgentsAgents.php

@@ -0,0 +1,28 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudio UNA Studio
+ * @{
+ */
+
+class BxDolStudioAgentsAgents extends BxTemplStudioGridAgents
+{
+    protected $_oDb;
+
+    protected $_iProfileIdAi;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sDefaultSortingOrder = 'DESC';
+
+        $this->_oDb = new BxDolStudioAgentsQuery();
+
+        $this->_iProfileIdAi = BxDolAI::getInstance()->getProfileId();
+    }
+}
+
+/** @} */

+ 14 - 0
studio/js/agents.js

@@ -122,6 +122,20 @@ BxDolStudioPageAgents.prototype.assistantDelete = function(oButton) {
     $(oButton).parents('.bx-form-input-assistant:first').remove();
 };
 
+BxDolStudioPageAgents.prototype.toolAdd = function(oButton, sName) {
+    var oButton = $(oButton);
+
+    var oSubentry = oButton.parents('#bx-form-element-' + sName).find('.bx-form-input-tools:first').clone();
+    oSubentry.find("select").val('');
+    oSubentry.find("input[type = 'hidden']").remove();
+
+    oButton.parents('.bx-form-input-tools-add:first').before(oSubentry);
+};
+
+BxDolStudioPageAgents.prototype.toolDelete = function(oButton) {
+    $(oButton).parents('.bx-form-input-tools:first').remove();
+};
+
 BxDolStudioPageAgents.prototype._getDefaultData = function() {
     var oDate = new Date();
     return jQuery.extend({}, this._oRequestParams, {_t:oDate.getTime()});

+ 11 - 0
studio/template/agents_agents_form_tools.html

@@ -0,0 +1,11 @@
+<div class="bx-form-input-tools-cont">
+    <bx_repeat:tools>
+        <div class="bx-form-input-tools bx-def-padding-thd-topbottom">
+            <div class="bx-form-input-tools-cell bx-fi-ac-txt">__input_select__</div>
+            <div class="bx-form-input-tools-cell bx-fi-ac-btn bx-def-padding-sec-left">
+                <a class="bx-btn" href="javascript:void(0)" onclick="javascript:__js_object__.toolDelete(this)"><bx_image_auto:times /></a>
+            </div>
+        </div>
+    </bx_repeat:tools>
+    <div class="bx-form-input-tools-add">__btn_add__</div>
+</div>

+ 26 - 1
studio/template/css/agents.css

@@ -42,6 +42,31 @@
     float: none;
 }
 
+/*-- Automators form: Tools --*/
+.bx-form-input-tools {
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+.bx-form-input-tools-cell.bx-fi-pc-txt {
+    -webkit-flex: 1 1 auto;
+    -ms-flex: 1 1 auto;
+    flex: 1 1 auto;
+}
+.bx-form-input-tools-cell.bx-fi-pc-btn {
+    -webkit-flex: 0 0 auto;
+    -ms-flex: 0 0 auto;
+    flex: 0 0 auto;
+}
+.bx-form-input-tools-add {
+    text-align: right;
+}
+.bx-form-input-tools-add .bx-form-input-button {
+    display: inline-block;
+    float: none;
+}
+
 /*-- Automators form: Helpers --*/
 .bx-form-input-helper {
     display: -webkit-flex;
@@ -90,4 +115,4 @@
 .bx-form-input-assistant-add .bx-form-input-button {
     display: inline-block;
     float: none;
-}
+}

+ 8 - 2
studio/template/scripts/BxBaseStudioAgents.php

@@ -37,7 +37,8 @@ class BxBaseStudioAgents extends BxDolStudioAgents
             BX_DOL_STUDIO_AGENTS_TYPE_TOOLS => ['icon' => 'mi-agt-helpers.svg'],
             BX_DOL_STUDIO_AGENTS_TYPE_EMBEDDING_PROVIDERS => ['icon' => 'mi-agt-helpers.svg'],
             BX_DOL_STUDIO_AGENTS_TYPE_VECTOR_STORE => ['icon' => 'mi-agt-helpers.svg'],
-            BX_DOL_STUDIO_AGENTS_TYPE_ASSISTANTS => ['icon' => 'mi-agt-assistants.svg'],
+            BX_DOL_STUDIO_AGENTS_TYPE_AGENTS => ['icon' => 'mi-agt-assistants.svg'],
+
             /*
              * Hidden for now. Most probably they will be removed.
              * 
@@ -54,7 +55,7 @@ class BxBaseStudioAgents extends BxDolStudioAgents
             BX_DOL_STUDIO_AGENTS_TYPE_ASSISTANTS => 'sys_studio_agents_assistants',
             BX_DOL_STUDIO_AGENTS_TYPE_ASSISTANTS . '_chats' => 'sys_studio_agents_assistants_chats',
             BX_DOL_STUDIO_AGENTS_TYPE_ASSISTANTS . '_files' => 'sys_studio_agents_assistants_files',
-
+            BX_DOL_STUDIO_AGENTS_TYPE_AGENTS => 'sys_studio_agents_agents',
             /*
              * Hidden for now. Most probably they will be removed.
              * 
@@ -229,6 +230,11 @@ class BxBaseStudioAgents extends BxDolStudioAgents
         return $this->getGrid($this->aGridObjects[BX_DOL_STUDIO_AGENTS_TYPE_ASSISTANTS]);
     }
 
+    protected function getAgents()
+    {
+        return $this->getGrid($this->aGridObjects[BX_DOL_STUDIO_AGENTS_TYPE_AGENTS]);
+    }
+
     /*
      * Isn't used for now. Most probably they will be removed.
      * 

+ 590 - 0
studio/template/scripts/BxBaseStudioAgentsAgents.php

@@ -0,0 +1,590 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxBaseStudioAgentsAgents extends BxDolStudioAgentsAgents
+{
+    protected $_sUrlPage;
+    protected $_sFieldName;
+
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+
+        $this->_sUrlPage = BX_DOL_URL_STUDIO . 'agents.php?page=agents';
+
+        $this->_sFieldName = 'name';
+    }
+
+    public function getPageJsObject()
+    {
+        return 'oBxDolStudioPageAgents';
+    }
+
+    public function performActionAdd()
+    {
+        $sAction = 'add';
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $oAI = BxDolAI::getInstance();
+
+        $aForm = $this->_getForm($sAction);
+        $oForm = new BxTemplFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = [
+                'added' => time()
+            ];
+
+            $sName = $oForm->getCleanValue($this->_sFieldName);
+            $sName = $this->_getUniqName($sName);
+            BxDolForm::setSubmittedValue($this->_sFieldName, $sName, $oForm->aFormAttrs['method']);
+
+            $iProfileId = $oForm->getCleanValue('profile_id');
+            if(empty($iProfileId)) {
+                $iProfileId = (int)getParam('sys_agents_profile');
+                if(empty($iProfileId))
+                    $iProfileId = current(bx_srv('system', 'get_options_agents_profile', [false], 'TemplServices'))['key'];
+
+                $aValsToAdd['profile_id'] = $iProfileId;
+            }
+  
+            $iModel = $oForm->getCleanValue('model_id');
+            $sTrigger = $oForm->getCleanValue('trigger');
+
+            $sTools = is_array($oForm->getCleanValue('tools')) ? implode(',', $oForm->getCleanValue('tools')) : '';
+            $aValsToAdd['tools'] = $sTools;
+
+            $bIsValid = true;
+            if($bIsValid) {
+                if(($iId = $oForm->insert($aValsToAdd)) !== false) {
+                    $aRes = ['grid' => $this->getCode(false), 'blink' => $iId];
+                }
+                else
+                    $aRes = ['msg' => _t('_sys_txt_error_occured')];
+
+                return echoJson($aRes);
+            }
+        }
+
+        $sFormId = $oForm->getId();
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($sFormId . '_popup', _t('_sys_agents_agents_popup_add'), $this->_oTemplate->parseHtmlByName('agents_automator_form.html', [
+            'form_id' => $sFormId,
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    public function performActionEdit()
+    {
+        $sAction = 'edit';
+
+        $oTemplate = BxDolStudioTemplate::getInstance();
+        $oAI = BxDolAI::getInstance();
+
+        $iId = $this->_getId();
+        $aAutomator = $oAI->getAutomator($iId);
+        $aAutomator['providers'] = $this->_oDb->getAutomatorsBy(['sample' => 'providers_by_id_pairs', 'id' => $iId]);
+        $aAutomator['helpers'] = $this->_oDb->getAutomatorsBy(['sample' => 'helpers_by_id_pairs', 'id' => $iId]);
+
+        $aForm = $this->_getFormEdit($sAction, $aAutomator);
+        $oForm = new BxTemplFormView($aForm);
+        $oForm->initChecker();
+
+        if($oForm->isSubmittedAndValid()) {
+            $aValsToAdd = [];
+
+            $sName = $oForm->getCleanValue($this->_sFieldName);
+            if($aAutomator[$this->_sFieldName] != $sName) {
+                $sName = $this->_getUniqName($sName);
+                BxDolForm::setSubmittedValue($this->_sFieldName, $sName, $oForm->aFormAttrs['method']);
+            }
+
+            /**
+             * Process Providers
+             */
+            $aProvidersIds = $oForm->getCleanValue('providers_ids');
+            $bProvidersIds = !empty($aProvidersIds) && is_array($aProvidersIds);
+            $aProvidersValues = $oForm->getCleanValue('providers');
+            $bProvidersValues = !empty($aProvidersValues) && is_array($aProvidersValues);
+
+            //--- Providers: Remove deleted
+            if(!empty($aAutomator['providers']) && is_array($aAutomator['providers']))
+                $this->_oDb->deleteAutomatorProvidersById(array_diff(array_keys($aAutomator['providers']), $bProvidersIds ? $aProvidersIds : []));
+
+            //--- Providers: Update existed
+            if($bProvidersIds)
+                foreach($aProvidersIds as $iIndex => $iApId)
+                    $this->_oDb->updateAutomatorProvider(['provider_id' => (int)$aProvidersValues[$iIndex]], ['id' => (int)$iApId]);
+
+            //--- Providers: Add new
+            $iProvidersIds = $bProvidersIds ? count($aProvidersIds) : 0;
+            $iProvidersValues = $bProvidersValues ? count($aProvidersValues) : 0;
+            if($iProvidersValues > $iProvidersIds) {
+                $aProvidersValues = array_slice($aProvidersValues, $iProvidersIds);
+                foreach($aProvidersValues as $iProvidersValue)
+                    $this->_oDb->insertAutomatorProvider([
+                        'automator_id' => $iId,
+                        'provider_id' => (int)$iProvidersValue,
+                    ]);
+            }
+
+            /**
+             * Process Helpers
+             */
+            $aHelpersIds = $oForm->getCleanValue('helpers_ids');
+            $bHelpersIds = !empty($aHelpersIds) && is_array($aHelpersIds);
+            $aHelpersValues = $oForm->getCleanValue('helpers');
+            $bHelpersValues = !empty($aHelpersValues) && is_array($aHelpersValues);
+
+            //--- Helpers: Remove deleted
+            if(!empty($aAutomator['helpers']) && is_array($aAutomator['helpers']))
+                $this->_oDb->deleteAutomatorHelpersById(array_diff(array_keys($aAutomator['helpers']), $bHelpersIds ? $aHelpersIds : []));
+
+            //--- Helpers: Update existed
+            if($bHelpersIds)
+                foreach($aHelpersIds as $iIndex => $iAhId)
+                    $this->_oDb->updateAutomatorHelper(['helper_id' => (int)$aHelpersValues[$iIndex]], ['id' => (int)$iAhId]);
+
+            //--- Helpers: Add new
+            $iHelpersIds = $bHelpersIds ? count($aHelpersIds) : 0;
+            $iHelpersValues = $bHelpersValues ? count($aHelpersValues) : 0;
+            if($iHelpersValues > $iHelpersIds) {
+                $aHelpersValues = array_slice($aHelpersValues, $iHelpersIds);
+                foreach($aHelpersValues as $iHelpersValue)
+                    $this->_oDb->insertAutomatorHelper([
+                        'automator_id' => $iId,
+                        'helper_id' => (int)$iHelpersValue,
+                    ]);
+            }
+
+            /**
+             * Process Assistants
+             */
+            $aAssistantsIds = $oForm->getCleanValue('assistants_ids');
+            $bAssistantsIds = !empty($aAssistantsIds) && is_array($aAssistantsIds);
+            $aAssistantsValues = $oForm->getCleanValue('assistants');
+            $bAssistantsValues = !empty($aAssistantsValues) && is_array($aAssistantsValues);
+
+            //--- Assistants: Remove deleted
+            if(!empty($aAutomator['assistants']) && is_array($aAutomator['assistants']))
+                $this->_oDb->deleteAutomatorAssistantsById(array_diff(array_keys($aAutomator['assistants']), $bAssistantsIds ? $aAssistantsIds : []));
+
+            //--- Assistants: Update existed
+            if($bAssistantsIds)
+                foreach($aAssistantsIds as $iIndex => $iAhId)
+                    $this->_oDb->updateAutomatorAssistant(['assistant_id' => (int)$aAssistantsValues[$iIndex]], ['id' => (int)$iAhId]);
+
+            //--- Assistants: Add new
+            $iAssistantsIds = $bAssistantsIds ? count($aAssistantsIds) : 0;
+            $iAssistantsValues = $bAssistantsValues ? count($aAssistantsValues) : 0;
+            if($iAssistantsValues > $iAssistantsIds) {
+                $aAssistantsValues = array_slice($aAssistantsValues, $iAssistantsIds);
+                foreach($aAssistantsValues as $iAssistantsValue)
+                    $this->_oDb->insertAutomatorAssistant([
+                        'automator_id' => $iId,
+                        'assistant_id' => (int)$iAssistantsValue,
+                    ]);
+            }
+
+            $iProfileId = $oForm->getCleanValue('profile_id');
+            if(empty($iProfileId)) {
+                $iProfileId = (int)getParam('sys_agents_profile');
+                if(empty($iProfileId))
+                    $iProfileId = current(bx_srv('system', 'get_options_agents_profile', [false], 'TemplServices'))['key'];
+
+                $aValsToAdd['profile_id'] = $iProfileId;
+            }
+
+            $sSchedulerTime = $oForm->getCleanValue('scheduler_time');
+            if(!empty($sSchedulerTime))
+                $aValsToAdd['params'] = json_encode(['scheduler_time' => $sSchedulerTime]);
+
+            if($oForm->update($iId, $aValsToAdd) !== false) {
+                if(($oCmts = BxDolAI::getInstance()->getAutomatorCmtsObject($iId, $oTemplate)) !== null) {
+                    $sInstructions = $oAI->getAutomatorInstruction('profile', $iProfileId);
+
+                    $aProviders = $this->_oDb->getAutomatorsBy(['sample' => 'providers_by_id_pairs', 'id' => $iId]);
+                    if(!empty($aProviders) && is_array($aProviders))
+                        $sInstructions .= $oAI->getAutomatorInstruction('providers', array_values($aProviders));
+
+                    $aHelpers = $this->_oDb->getAutomatorsBy(['sample' => 'helpers_by_id_pairs', 'id' => $iId]);
+                    if(!empty($aHelpers) && is_array($aHelpers))
+                        $sInstructions .= $oAI->getAutomatorInstruction('helpers', array_values($aHelpers));
+
+                    $aAssistants = $this->_oDb->getAutomatorsBy(['sample' => 'assistants_by_id_pairs', 'id' => $iId]);
+                    if(!empty($aAssistants) && is_array($aAssistants))
+                        $sInstructions .= $oAI->getAutomatorInstruction('assistants', array_values($aAssistants));
+
+                    $oCmts->addAuto([
+                        'cmt_author_id' => $iProfileId,
+                        'cmt_parent_id' => 0,
+                        'cmt_text' => $sInstructions
+                    ]);
+
+                    if(($oMessage = new BxDolAIMessageString('hb', $sInstructions)) && ($sResponse = $oAI->getModelObject($aAutomator['model_id'])->getResponse($aAutomator['type'], $oMessage, $aAutomator['params'])) !== false) {
+                        sleep(1);
+                        $oCmts->addAuto([
+                            'cmt_author_id' => $this->_iProfileIdAi,
+                            'cmt_parent_id' => 0,
+                            'cmt_text' => $sResponse
+                        ]);
+                    }
+                }
+
+                $aRes = ['grid' => $this->getCode(false), 'blink' => $iId];
+            }
+            else
+                $aRes = ['msg' => _t('_sys_txt_error_occured')];
+
+            return echoJson($aRes);
+        } 
+
+        $sFormId = $oForm->getId();
+        $sContent = BxTemplStudioFunctions::getInstance()->popupBox($sFormId . '_popup', _t('_sys_agents_automators_popup_edit'), $this->_oTemplate->parseHtmlByName('agents_automator_form.html', [
+            'form_id' => $sFormId,
+            'form' => $oForm->getCode(true),
+            'object' => $this->_sObject,
+            'action' => $sAction
+        ]));
+
+        return echoJson(['popup' => ['html' => $sContent, 'options' => ['closeOnOuterClick' => false]]]);
+    }
+
+    protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
+    {
+        // if(empty($aRow['code']) || $aRow['status'] != BX_DOL_AI_AUTOMATOR_STATUS_READY)
+        //    return parent::_getCellDefault('', $sKey, $aField, $aRow);
+
+        return parent::_getCellSwitcher ($mixedValue, $sKey, $aField, $aRow);
+    }
+
+    protected function _getCellType($mixedValue, $sKey, $aField, $aRow)
+    {
+        return parent::_getCellDefault($mixedValue, $sKey, $aField, $aRow);
+    }
+    
+    protected function _delete ($mixedId)
+    {
+        $mixedResult = parent::_delete($mixedId);
+        if($mixedResult) {
+            // $this->_oDb->deleteAutomatorProviders(['automator_id' => (int)$mixedId]);
+            // $this->_oDb->deleteAutomatorHelpers(['automator_id' => (int)$mixedId]);
+            // $this->_oDb->deleteAutomatorAssistants(['automator_id' => (int)$mixedId]);
+
+            // if(($oCmts = BxDolAI::getInstance()->getAutomatorCmtsObject($mixedId)) !== false)
+            //    $oCmts->onObjectDelete();
+        }
+
+        return $mixedResult;
+    }
+
+    protected function _getFormEdit($sAction, $aAgent = [])
+    {
+        $aForm = $this->_getForm($sAction, $aAgent);
+        $aForm['form_attrs']['action'] .= '&id=' . $aAgent['id'];
+
+        unset($aForm['inputs']['type']);
+        unset($aForm['inputs']['message']);
+
+        return $aForm;
+    }
+
+    protected function _getForm($sAction, $aAgent = [])
+    {
+        $aForm = array(
+            'form_attrs' => array(
+                'id' => 'bx_std_agents_automators_' . $sAction,
+                'action' => BX_DOL_URL_ROOT . 'grid.php?o=sys_studio_agents_agents&a=' . $sAction,
+                'method' => 'post',
+            ),
+            'params' => array (
+                'db' => array(
+                    'table' => 'sys_agents_agents',
+                    'key' => 'id',
+                    'submit_name' => 'do_submit',
+                ),
+            ),
+            'inputs' => array(
+                'name' => [
+                    'type' => 'text',
+                    'name' => 'name',
+                    'caption' => _t('_sys_agents_automators_field_name'),
+                    'info' => _t('_sys_agents_automators_field_name_info'),
+                    'value' => isset($aAgent['name']) ? $aAgent['name'] : '',
+                    'required' => '1',
+                    'checker' => [
+                        'func' => 'Avail',
+                        'params' => [],
+                        'error' => _t('_sys_agents_form_field_err_enter'),
+                    ],
+                    'db' => [
+                        'pass' => 'Xss',
+                    ],
+                ],
+                'model_id' => [
+                    'type' => 'select',
+                    'name' => 'model_id',
+                    'caption' => _t('_sys_agents_automators_field_model_id'),
+                    'info' => _t('_sys_agents_automators_field_model_id_info'),
+                    'value' => isset($aAgent['model_id']) ? $aAgent['model_id'] : BxDolAI::getDefaultModel(),
+                    'values' => $this->_oDb->getModelsBy(['sample' => 'all_pairs', 'active' => 1, 'capabilities' => ['chatvlm', 'chatllm']]),
+                    'required' => '1',
+                    'checker' => [
+                        'func' => 'Avail',
+                        'params' => [],
+                        'error' => _t('_sys_agents_form_field_err_select'),
+                    ],
+                    'db' => [
+                        'pass' => 'Int',
+                    ],
+                ],
+                'profile_id' => [
+                    'type' => 'select',
+                    'name' => 'profile_id',
+                    'caption' => _t('_sys_agents_field_profile_id'),
+                    'info' => _t('_sys_agents_field_profile_id_inf'),
+                    'value' => isset($aAgent['profile_id']) ? $aAgent['profile_id'] : 0,
+                    'values' => bx_srv('system', 'get_options_agents_profile', [], 'TemplServices'),
+                    'required' => '0',
+                    'db' => [
+                        'pass' => 'Int',
+                    ]
+                ],
+                'trigger' => [
+                    'type' => 'select',
+                    'name' => 'trigger',
+                    'caption' => _t('_sys_agents_automators_field_trigger'),
+                    'info' => _t('_sys_agents_automators_field_trigger_info'),                    
+                    'value' => isset($aAgent['trigger']) ? $aAgent['trigger'] : 0,
+                    'values' => [
+                        'alert' => 'alert',
+                        'scheduler' => 'scheduler',
+                        'webhook' => 'webhook',
+                        'manual' => 'manual',
+                        'agent' => 'agent',
+                        'message' => 'message',
+                    ],
+                    'attrs' => [
+                        'onchange' => $this->getPageJsObject() . '.onChangeAgentTrigger(this)',
+                    ],
+                    'required' => '1',
+                    'db' => [
+                        'pass' => 'Xss',
+                    ]
+                ],
+                'alert_sample' => [
+                    'type' => 'custom',
+                    'name' => 'alert_sample',
+                    'caption' => _t('_sys_agents_field_alert_sample'),
+                    'content' => isset($aAgent['alert_sample']) ? $aAgent['alert_sample'] : _t('_sys_agents_waiting_for_sample_data'),
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'alert' ? 'display:none' : ''
+                    // ],
+                ],
+                'alert_unit' => [
+                    'type' => 'text',
+                    'name' => 'alert_unit',
+                    'caption' => _t('_sys_agents_field_alert_unit'),
+                    'info' => _t('_sys_agents_field_alert_unit_info'),
+                    'value' => isset($aAgent['alert_unit']) ? $aAgent['alert_unit'] : '',
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'alert' ? 'display:none' : ''
+                    // ],
+                    'db' => [
+                        'pass' => 'Xss',
+                    ],
+                ],
+                'alert_action' => [
+                    'type' => 'text',
+                    'name' => 'alert_action',
+                    'caption' => _t('_sys_agents_field_alert_action'),
+                    'value' => isset($aAgent['alert_action']) ? $aAgent['alert_action'] : '',
+                    // 'tr_attrs' => [
+                    //    'style' => $sType != 'alert' ? 'display:none' : ''
+                    // ],
+                    'db' => [
+                        'pass' => 'Xss',
+                    ],
+                ],
+                'scheduler_cron' => [
+                    'type' => 'text',
+                    'name' => 'scheduler_cron',
+                    'caption' => _t('_sys_agents_automators_field_scheduler_cron'),
+                    'info' => _t('_sys_agents_automators_field_scheduler_cron_info'),
+                    'value' => isset($aAgent['scheduler_cron']) ? $aAgent['scheduler_cron'] : '',
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'scheduler' ? 'display:none' : ''
+                    // ],
+                ],
+                'webhook_sample' => [
+                    'type' => 'custom',
+                    'name' => 'webhook_sample',
+                    'caption' => _t('_sys_agents_field_webhook_sample'),
+                    'content' => isset($aAgent['webhook_sample']) ? $aAgent['webhook_sample'] : _t('_sys_agents_waiting_for_sample_data'),
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'webhook' ? 'display:none' : ''
+                    // ],
+                ],
+                'webhook_key' => [
+                    'type' => 'text',
+                    'name' => 'webhook_key',
+                    'caption' => _t('_sys_agents_automators_field_webhook_key'),
+                    'info' => _t('_sys_agents_automators_field_webhook_key_info'),
+                    'value' => isset($aAgent['webhook_key']) ? $aAgent['webhook_key'] : '',
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'webhook' ? 'display:none' : ''
+                    // ],
+                ],
+                'message_profile_id' => [
+                    'type' => 'select',
+                    'name' => 'message_profile_id',
+                    'caption' => _t('_sys_agents_automators_field_message_profile'),
+                    'info' => _t('_sys_agents_automators_field_message_profile_info'),
+                    'value' => isset($aAgent['message_profile_id']) ? $aAgent['message_profile_id'] : '',
+                    // 'tr_attrs' => [
+                    //     'style' => $sType != 'message' ? 'display:none' : ''
+                    // ],
+                ],
+                'vector_store_id' => [
+                    'type' => 'select',
+                    'name' => 'vector_store_id',
+                    'caption' => _t('_sys_agents_automators_field_vector_store_id'),
+                    'info' => _t('_sys_agents_automators_field_vector_store_id_info'),
+                    'value' => isset($aAgent['vector_store_id']) ? $aAgent['vector_store_id'] : 0,
+                    'values' => $this->_oDb->getVectorStores(),
+                ],
+                'tools' => [
+                    'type' => 'custom',
+                    'name' => 'tools',
+                    'caption' => _t('_sys_agents_automators_field_tools'),
+                    'info' => _t('_sys_agents_automators_field_tools_info'),
+                    'value' => '',
+                ],
+                'prompt_system' => [
+                    'type' => 'textarea',
+                    'name' => 'prompt_system',
+                    'caption' => _t('_sys_agents_automators_field_prompt_system'),
+                    'info' => _t('_sys_agents_automators_field_prompt_system_info'),
+                    'value' => isset($aAgent['prompt_system']) ? $aAgent['prompt_system'] : '',
+                ],
+                'prompt_steps' => [
+                    'type' => 'textarea',
+                    'name' => 'prompt_steps',
+                    'caption' => _t('_sys_agents_automators_field_prompt_steps'),
+                    'info' => _t('_sys_agents_automators_field_prompt_steps_info'),
+                    'value' => isset($aAgent['prompt_steps']) ? $aAgent['prompt_steps'] : '',
+                ],
+                'prompt_output' => [
+                    'type' => 'textarea',
+                    'name' => 'prompt_output',
+                    'caption' => _t('_sys_agents_automators_field_prompt_output'),
+                    'info' => _t('_sys_agents_automators_field_prompt_output_info'),
+                    'value' => isset($aAgent['prompt_output']) ? $aAgent['prompt_output'] : '',
+                ],
+                'prompt_tools' => [
+                    'type' => 'textarea',
+                    'name' => 'prompt_tools',
+                    'caption' => _t('_sys_agents_automators_field_prompt_tools'),
+                    'info' => _t('_sys_agents_automators_field_prompt_tools_info'),
+                    'value' => isset($aAgent['prompt_tools']) ? $aAgent['prompt_tools'] : '',
+                ],
+                'submit' => $this->_getFormControls(),
+
+            ),
+        );
+        
+        $this->_getMultiField('tools', $aAgent, 'getTools', 'toolAdd', 'agents_agents_form_tools.html', $aForm);
+
+        return $aForm;
+    }
+
+    protected function _getUniqName($sName)
+    {
+        return uriGenerate($sName, 'sys_agents_automators', 'name', ['lowercase' => false]);
+    }
+
+    protected function _getMultiField($sField, $aAgent, $sGetValuesMethod, $sJsAddMethod, $sTemplateName, &$aForm)
+    {
+        if(!isset($aForm['inputs']['tools'])) 
+            return;
+
+        $sJsObject = $this->getPageJsObject();
+
+        $oForm = new BxTemplFormView([]);
+
+        $aValues = ['0' => _t('_sys_please_select')];
+        $a = $this->_oDb->$sGetValuesMethod();
+        foreach ($a as $k => $v) {
+            $aValues[$k] = $v;
+        }
+
+        $aTmplVars = [];
+        if(!empty($aAgent[$sField])) {
+            $a = explode(',', $aAgent[$sField]);
+            foreach($a as $v) {
+                $aInputSelect = [
+                    'type' => 'select',
+                    'name' => $sField . '[]',
+                    'values' => $aValues,
+                    'value' => $v,
+                    'attrs' => [
+                        'class' => 'bx-def-margin-sec-top-auto'
+                    ]
+                ];
+                $sInput = $oForm->genInput($aInputSelect);
+
+                $aInputHidden = [
+                    'type' => 'hidden',
+                    'name' => $sField . '_ids[]',
+                    'value' => $v,
+                ];
+                $sInput .= $oForm->genInput($aInputHidden);
+
+                $aTmplVars[] = ['js_object' => $sJsObject, 'input_select' => $sInput];
+            }
+        }
+        else  {
+            $aInputSelect = [
+                'type' => 'select',
+                'name' => $sField . '[]',
+                'values' => $aValues,
+                'value' => '',
+                'attrs' => [
+                    'class' => 'bx-def-margin-sec-top-auto'
+                ]
+            ];
+
+            $aTmplVars = [
+                ['js_object' => $sJsObject, 'input_select' => $oForm->genInput($aInputSelect)],
+            ];
+        }
+        
+        $aInputButton = [
+            'type' => 'button',
+            'name' => $sField . '_add',
+            'value' => _t('_adm_nav_btn_items_add'),
+            'attrs' => [
+                'class' => 'bx-def-margin-sec-top',
+                'onclick' => $sJsObject . ".$sJsAddMethod(this, '" . $sField . "');"
+            ]
+        ];
+
+        $aForm['inputs'][$sField]['content'] = $this->_oTemplate->parseHtmlByName($sTemplateName, [
+            "bx_repeat:$sField" => $aTmplVars,
+            'btn_add' => $oForm->genInputButton($aInputButton)
+        ]);
+    }
+}
+
+/** @} */

+ 19 - 0
studio/template/scripts_templ/BxTemplStudioAgentsAgents.php

@@ -0,0 +1,19 @@
+<?php defined('BX_DOL') or die('hack attempt');
+/**
+ * Copyright (c) UNA, Inc - https://una.io
+ * MIT License - https://opensource.org/licenses/MIT
+ *
+ * @defgroup    UnaStudioView UNA Studio Representation classes
+ * @ingroup     UnaStudio
+ * @{
+ */
+
+class BxTemplStudioAgentsAgents extends BxBaseStudioAgentsAgents
+{
+    public function __construct ($aOptions, $oTemplate = false)
+    {
+        parent::__construct ($aOptions, $oTemplate);
+    }
+}
+
+/** @} */

+ 36 - 0
webhook.php

@@ -0,0 +1,36 @@
+<?php
+
+define('BX_API', true);
+
+require_once('./inc/header.inc.php');
+require_once(BX_DIRECTORY_PATH_INC . "profiles.inc.php");
+require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
+
+bx_import('BxDolLanguages');
+
+header('Content-Type: application/json');
+
+$aHeaders = function_exists('getallheaders') ? getallheaders() : false;
+if ($aHeaders) {
+    $sAuthHeader = isset($aHeaders['Authorization']) ? $aHeaders['Authorization'] : (isset($aHeaders['authorization']) ? $aHeaders['authorization'] : false);
+}
+else {
+    $sAuthHeader = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : false;
+}
+$sKey = str_replace('Bearer ', '', $sAuthHeader);
+
+$oAi = BxDolAI::getInstance();
+$aAgent = $sKey ? $oAi->getAgentByTriggerKey($sKey) : false;
+if (!$aAgent) {
+    header('HTTP/1.0 403 Forbidden');
+    BxDolLanguages::getInstance();
+    echo json_encode(['status' => 403, 'error' => _t("_Access denied")]);
+    exit;
+}
+
+$aParams = $_REQUEST;
+$aParams['trigger'] = 'webhook';
+
+$mixed = $oAi->callAgent('webhook', $aAgent, $aParams);
+
+echo json_encode($mixed);