Explorar o código

#5654 AI agents improvements (split non-logged)

romanlesnikov hai 1 mes
pai
achega
7889415c94

+ 72 - 2
inc/classes/BxDolAI.php

@@ -487,13 +487,54 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         return '';
     }
 
+    /**
+     * Guest threads: `g:{clientKey}` (NEO header) or `s:{memberSession}` (UNA cookie).
+     * Members: profile id. Guest key is kept in session and adopted on login.
+     */
+    public function resolveChatHistoryParams()
+    {
+        $iProfileId = (int)bx_get_logged_profile_id();
+        $oSession = BxDolSession::getInstance();
+        $sHeaderKey = $this->readAiGuestKeyFromRequest();
+        $sSessionKey = 'ai_chat_guest_subindex';
+
+        if ($iProfileId) {
+            $sGuest = $sHeaderKey ? ('g:' . $sHeaderKey) : '';
+            if ($sGuest === '') {
+                $sStored = $oSession->getValue($sSessionKey);
+                if ($this->isAiGuestSubindex($sStored))
+                    $sGuest = $sStored;
+            }
+            if ($sGuest !== '')
+                $this->_oDb->adoptGuestChatHistory($sGuest, $iProfileId);
+            if ($oSession->isValue($sSessionKey))
+                $oSession->unsetValue($sSessionKey);
+
+            return [
+                'sender_profile_id' => $iProfileId,
+                'chat_history_subindex' => (string)$iProfileId,
+            ];
+        }
+
+        $oSession->start(true);
+        $sSessionId = (string)$oSession->getId();
+        $sSubindex = $sHeaderKey ? ('g:' . $sHeaderKey) : ('s:' . ($sSessionId !== '' ? $sSessionId : '0'));
+        $oSession->setValue($sSessionKey, $sSubindex);
+
+        return [
+            'sender_profile_id' => 0,
+            'chat_history_subindex' => $sSubindex,
+        ];
+    }
+
     /**
      * Transcript for TanStack `useChat` hydrate / `initialMessages`.
      * Loads the same NeuronAI chat history the agent uses when streaming.
      */
     public function getChatHistoryUiMessages($iAgentId, $aParams = [])
     {
-        $aParams['chat_history_subindex'] = (int)($aParams['sender_profile_id'] ?? 0);
+        if (!isset($aParams['chat_history_subindex']))
+            $aParams = array_merge($this->resolveChatHistoryParams(), $aParams);
 
         $aAgent = BxDolAiQuery::getAgentObject((int)$iAgentId);
         if (!$aAgent || empty($aAgent['chat_history_context']))
@@ -567,9 +608,38 @@ class BxDolAI extends BxDolFactory implements iBxDolSingleton
         return $aResult;
     }
 
+    protected function readAiGuestKeyFromRequest()
+    {
+        $s = $_SERVER['HTTP_X_UNA_AI_GUEST_KEY'] ?? '';
+        if ($s === '' && function_exists('apache_request_headers')) {
+            foreach (apache_request_headers() as $sName => $sValue) {
+                if (strtolower((string)$sName) === 'x-una-ai-guest-key') {
+                    $s = $sValue;
+                    break;
+                }
+            }
+        }
+        return $this->sanitizeAiGuestKey($s);
+    }
+
+    protected function sanitizeAiGuestKey($s)
+    {
+        $s = is_string($s) ? $s : '';
+        return preg_match('/^[A-Za-z0-9_-]{16,64}$/', $s) ? $s : '';
+    }
+
+    protected function isAiGuestSubindex($s)
+    {
+        $s = is_string($s) ? $s : '';
+        if ($s === '' || (strncmp($s, 'g:', 2) !== 0 && strncmp($s, 's:', 2) !== 0))
+            return false;
+        return $this->sanitizeAiGuestKey(substr($s, 2)) !== '';
+    }
+
     public function streamAgentChat($iAgentId, $sPrompt, $aParams = [], $sThreadId = null)
     {
-        $aParams['chat_history_subindex'] = (int)($aParams['sender_profile_id'] ?? 0);
+        if (!isset($aParams['chat_history_subindex']))
+            $aParams = array_merge($this->resolveChatHistoryParams(), $aParams);
 
         $oAdapter = new NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter($sThreadId);
 

+ 50 - 0
inc/classes/BxDolAIQuery.php

@@ -986,6 +986,56 @@ class BxDolAIQuery extends BxDolDb
         return $iAffected;
     }
 
+    /**
+     * Rename guest threads (`…:g:{key}` / `…:s:{session}`) onto a profile id.
+     * If the member thread already has messages, drop the guest copy.
+     */
+    public function adoptGuestChatHistory($sGuestSubindex, $iProfileId)
+    {
+        $sGuestSubindex = (string)$sGuestSubindex;
+        $iProfileId = (int)$iProfileId;
+        if ($sGuestSubindex === '' || !$iProfileId)
+            return 0;
+
+        $aRows = $this->getAll("SELECT `thread_id` FROM `sys_agents_chat_history`");
+        if (!$aRows)
+            return 0;
+
+        $sSuffix = ':' . $sGuestSubindex;
+        $iSuffixLen = strlen($sSuffix);
+        $iAffected = 0;
+        foreach ($aRows as $aRow) {
+            $sOld = $aRow['thread_id'] ?? '';
+            if ($sOld === '' || substr($sOld, -$iSuffixLen) !== $sSuffix)
+                continue;
+
+            $sNew = substr($sOld, 0, -$iSuffixLen) . ':' . $iProfileId;
+            if ($sNew === $sOld)
+                continue;
+
+            $sExisting = $this->getOne("SELECT `messages` FROM `sys_agents_chat_history` WHERE `thread_id` = :t", [
+                't' => $sNew,
+            ]);
+            $aExisting = json_decode((string)$sExisting, true);
+            if (is_array($aExisting) && $aExisting !== []) {
+                $iAffected += (int)$this->query("DELETE FROM `sys_agents_chat_history` WHERE `thread_id` = :t", [
+                    't' => $sOld,
+                ]);
+                continue;
+            }
+
+            if ($sExisting !== false && $sExisting !== null)
+                $this->query("DELETE FROM `sys_agents_chat_history` WHERE `thread_id` = :t", ['t' => $sNew]);
+
+            $iAffected += (int)$this->query("UPDATE `sys_agents_chat_history` SET `thread_id` = :n WHERE `thread_id` = :o", [
+                'n' => $sNew,
+                'o' => $sOld,
+            ]);
+        }
+
+        return $iAffected;
+    }
+
     static public function getAlert($s) {
         if (!$s)
             return false;        

+ 2 - 2
inc/classes/BxDolAiAgent.php

@@ -124,8 +124,8 @@ class BxDolAiAgent extends RAG
     protected function getСhatHistoryThreadId(): string
     {
         $s = $this->aAgent['trigger'] . ':' . $this->aAgent['id'];
-        if (isset($this->aParams['chat_history_subindex']))
-            $s .=  ':' . $this->aParams['chat_history_subindex'];
+        if (isset($this->aParams['chat_history_subindex']) && $this->aParams['chat_history_subindex'] !== '')
+            $s .= ':' . (string)$this->aParams['chat_history_subindex'];
         return $s;
     }
 

+ 2 - 6
template/scripts/BxBaseServices.php

@@ -1827,9 +1827,7 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
         if (!$sPrompt)
             return echoJson(['code' => 400, 'msg' => _t('_sys_agents_json_field_err')]);
 
-        $oAi->streamAgentChat($aAgent['id'], $sPrompt, [
-            'sender_profile_id' => bx_get_logged_profile_id(),
-        ], $aData['threadId'] ?? null);
+        $oAi->streamAgentChat($aAgent['id'], $sPrompt, $oAi->resolveChatHistoryParams(), $aData['threadId'] ?? null);
     }
 
     /**
@@ -1847,9 +1845,7 @@ class BxBaseServices extends BxDol implements iBxDolProfileService
 
         $oAi = BxDolAI::getInstance();
         return echoJson([
-            'messages' => $oAi->getChatHistoryUiMessages($aAgent['id'], [
-                'sender_profile_id' => bx_get_logged_profile_id(),
-            ]),
+            'messages' => $oAi->getChatHistoryUiMessages($aAgent['id'], $oAi->resolveChatHistoryParams()),
             'activeRun' => null,
             'interrupts' => null,
         ]);