Plugin Directory

Changeset 3354820


Ignore:
Timestamp:
09/02/2025 03:09:54 PM (7 months ago)
Author:
oc3dots
Message:

Fix integrations conflict. Add tokens usage statistic

Location:
s2b-ai-assistant/trunk
Files:
12 edited

Legend:

Unmodified
Added
Removed
  • s2b-ai-assistant/trunk/lib/S2bAia.php

    r3338464 r3354820  
    4343
    4444            $charset_collate = $wpdb->get_charset_collate();
     45            //$table_name = $wpdb->prefix . 's2baia_instructions';
    4546           
     47            //require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    4648            $wpdb->query(/* phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange  *//* phpcs:ignore WordPress.DB.DirectDatabaseQuery */
    4749                    sprintf(
     
    5658                $charset_collate
    5759            ));
    58 
    59 
     60           
     61           
    6062            $wpdb->insert(/* phpcs:ignore WordPress.DB.DirectDatabaseQuery */
    6163                    $wpdb->prefix . 's2baia_instructions', array(
  • s2b-ai-assistant/trunk/lib/controllers/AdminChatBotController.php

    r3338464 r3354820  
    453453            }
    454454           
     455            if (isset($_POST['s2baia_chatbot_use_usage'])) {
     456                if($_POST['s2baia_chatbot_use_usage'] == 'on'){
     457                   $data['use_usage']  = 1;
     458                   update_option('s2baia_use_usage', 1);
     459                }else{
     460                   $data['use_usage']  = 0;
     461                   update_option('s2baia_use_usage', 0);
     462                }
     463            }else{
     464                $data['use_usage']  = 0;
     465                update_option('s2baia_use_usage', 0);
     466            }
     467           
    455468            if (isset($_POST['s2baia_chatbot_open_stream2'])) {
    456469                if($_POST['s2baia_chatbot_open_stream2'] == 'on'){
  • s2b-ai-assistant/trunk/lib/controllers/AdminController.php

    r3318367 r3354820  
    1414        public $chatbot_controller = false;
    1515        public $rag_controller = false;
     16        public $usage_controller = false;
    1617        public $admlogo = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjMiIGhlaWdodD0iMTMiIHZpZXdCb3g9IjAgMCAyMyAxMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcuMjg2OTIgMy44MTgxOEM3LjIyMyAzLjI3ODQxIDYuOTYzNzcgMi44NTkzNyA2LjUwOTIyIDIuNTYxMDhDNi4wNTQ2OCAyLjI2Mjc4IDUuNDk3MTUgMi4xMTM2MyA0LjgzNjY0IDIuMTEzNjNDNC4zNTM2OSAyLjExMzYzIDMuOTMxMSAyLjE5MTc2IDMuNTY4ODggMi4zNDgwMUMzLjIxMDIyIDIuNTA0MjYgMi45Mjk2OCAyLjcxOTEgMi43MjcyNiAyLjk5MjU0QzIuNTI4NCAzLjI2NTk4IDIuNDI4OTcgMy41NzY3IDIuNDI4OTcgMy45MjQ3MUMyLjQyODk3IDQuMjE1OSAyLjQ5ODIyIDQuNDY2MjYgMi42MzY3MSA0LjY3NTc4QzIuNzc4NzYgNC44ODE3NCAyLjk1OTg2IDUuMDUzOTcgMy4xODAwMyA1LjE5MjQ3QzMuNDAwMiA1LjMyNzQxIDMuNjMxMDMgNS40MzkyNyAzLjg3MjUxIDUuNTI4MDVDNC4xMTM5OCA1LjYxMzI4IDQuMzM1OTMgNS42ODI1MiA0LjUzODM0IDUuNzM1NzlMNS42NDYzIDYuMDM0MDlDNS45MzAzOSA2LjEwODY2IDYuMjQ2NDQgNi4yMTE2NCA2LjU5NDQ1IDYuMzQzMDRDNi45NDYwMSA2LjQ3NDQzIDcuMjgxNiA2LjY1Mzc2IDcuNjAxMiA2Ljg4MTAzQzcuOTI0MzUgNy4xMDQ3NSA4LjE5MDY5IDcuMzkyNCA4LjQwMDIxIDcuNzQzOTZDOC42MDk3MiA4LjA5NTUyIDguNzE0NDggOC41MjY5OCA4LjcxNDQ4IDkuMDM4MzVDOC43MTQ0OCA5LjYyNzg0IDguNTYwMDEgMTAuMTYwNSA4LjI1MTA2IDEwLjYzNjRDNy45NDU2NiAxMS4xMTIyIDcuNDk4MjIgMTEuNDkwNCA2LjkwODczIDExLjc3MDlDNi4zMjI3OSAxMi4wNTE1IDUuNjEwNzkgMTIuMTkxOCA0Ljc3MjcyIDEyLjE5MThDMy45OTE0NyAxMi4xOTE4IDMuMzE0OTggMTIuMDY1NyAyLjc0MzI0IDExLjgxMzZDMi4xNzUwNiAxMS41NjE0IDEuNzI3NjIgMTEuMjA5OSAxLjQwMDkyIDEwLjc1ODlDMS4wNzc3NiAxMC4zMDc5IDAuODk0ODc4IDkuNzg0MDkgMC44NTIyNjQgOS4xODc1SDIuMjE1OUMyLjI1MTQxIDkuNTk5NDMgMi4zODk5MSA5Ljk0MDM0IDIuNjMxMzggMTAuMjEwMkMyLjg3NjQxIDEwLjQ3NjYgMy4xODUzNiAxMC42NzU0IDMuNTU4MjMgMTAuODA2OEMzLjkzNDY1IDEwLjkzNDcgNC4zMzk0OCAxMC45OTg2IDQuNzcyNzIgMTAuOTk4NkM1LjI3Njk4IDEwLjk5ODYgNS43Mjk3NSAxMC45MTY5IDYuMTMxMDMgMTAuNzUzNUM2LjUzMjMxIDEwLjU4NjYgNi44NTAxMyAxMC4zNTU4IDcuMDg0NTEgMTAuMDYxMUM3LjMxODg4IDkuNzYyNzggNy40MzYwNyA5LjQxNDc3IDcuNDM2MDcgOS4wMTcwNEM3LjQzNjA3IDguNjU0ODMgNy4zMzQ4NiA4LjM2MDA4IDcuMTMyNDUgOC4xMzI4MUM2LjkzMDAzIDcuOTA1NTQgNi42NjM3IDcuNzIwODggNi4zMzM0NCA3LjU3ODgzQzYuMDAzMTkgNy40MzY3OSA1LjY0NjMgNy4zMTI1IDUuMjYyNzggNy4yMDU5NkwzLjkyMDQ1IDYuODIyNDRDMy4wNjgxNyA2LjU3NzQxIDIuMzkzNDYgNi4yMjc2MiAxLjg5NjMgNS43NzMwOEMxLjM5OTE0IDUuMzE4NTMgMS4xNTA1NiA0LjcyMzcyIDEuMTUwNTYgMy45ODg2M0MxLjE1MDU2IDMuMzc3ODQgMS4zMTU2OSAyLjg0NTE3IDEuNjQ1OTQgMi4zOTA2MkMxLjk3OTc1IDEuOTMyNTIgMi40MjcxOSAxLjU3NzQxIDIuOTg4MjcgMS4zMjUyOEMzLjU1MjkgMS4wNjk2IDQuMTgzMjMgMC45NDE3NTcgNC44NzkyNSAwLjk0MTc1N0M1LjU4MjM4IDAuOTQxNzU3IDYuMjA3MzggMS4wNjc4MiA2Ljc1NDI1IDEuMzE5OTVDNy4zMDExMyAxLjU2ODUzIDcuNzM0MzcgMS45MDk0NCA4LjA1Mzk3IDIuMzQyNjhDOC4zNzcxMiAyLjc3NTkyIDguNTQ3NTggMy4yNjc3NSA4LjU2NTMzIDMuODE4MThINy4yODY5MloiIGZpbGw9IiNGRjA2MDYiLz4KPHBhdGggZD0iTTE1LjMyMSAxMlYxLjA5MDkxSDE5LjEzNDlDMTkuODk0OSAxLjA5MDkxIDIwLjUyMTcgMS4yMjIzIDIxLjAxNTMgMS40ODUwOEMyMS41MDg5IDEuNzQ0MzEgMjEuODc2NCAyLjA5NDEgMjIuMTE3OSAyLjUzNDQ0QzIyLjM1OTQgMi45NzEyMyAyMi40ODAxIDMuNDU1OTYgMjIuNDgwMSAzLjk4ODYzQzIyLjQ4MDEgNC40NTczOCAyMi4zOTY3IDQuODQ0NDYgMjIuMjI5NyA1LjE0OTg1QzIyLjA2NjQgNS40NTUyNSAyMS44NDk4IDUuNjk2NzMgMjEuNTc5OSA1Ljg3NDI5QzIxLjMxMzYgNi4wNTE4NCAyMS4wMjQxIDYuMTgzMjMgMjAuNzExNiA2LjI2ODQ2VjYuMzc1QzIxLjA0NTQgNi4zOTYzIDIxLjM4MSA2LjUxMzQ5IDIxLjcxODQgNi43MjY1NkMyMi4wNTU3IDYuOTM5NjMgMjIuMzM4MSA3LjI0NTAyIDIyLjU2NTMgNy42NDI3NUMyMi43OTI2IDguMDQwNDggMjIuOTA2MiA4LjUyNjk4IDIyLjkwNjIgOS4xMDIyN0MyMi45MDYyIDkuNjQ5MTQgMjIuNzgyIDEwLjE0MSAyMi41MzM0IDEwLjU3NzhDMjIuMjg0OCAxMS4wMTQ2IDIxLjg5MjQgMTEuMzYwOCAyMS4zNTYyIDExLjYxNjVDMjAuODE5OSAxMS44NzIyIDIwLjEyMjIgMTIgMTkuMjYyOCAxMkgxNS4zMjFaTTE2LjY0MiAxMC44MjgxSDE5LjI2MjhDMjAuMTI1NyAxMC44MjgxIDIwLjczODMgMTAuNjYxMiAyMS4xMDA1IDEwLjMyNzRDMjEuNDY2MyA5Ljk5MDA1IDIxLjY0OTEgOS41ODE2NyAyMS42NDkxIDkuMTAyMjdDMjEuNjQ5MSA4LjczMjk1IDIxLjU1NSA4LjM5MjA0IDIxLjM2NjggOC4wNzk1NEMyMS4xNzg2IDcuNzYzNDkgMjAuOTEwNSA3LjUxMTM2IDIwLjU2MjUgNy4zMjMxNUMyMC4yMTQ1IDcuMTMxMzkgMTkuODAyNSA3LjAzNTUxIDE5LjMyNjcgNy4wMzU1MUgxNi42NDJWMTAuODI4MVpNMTYuNjQyIDUuODg0OTRIMTkuMDkyM0MxOS40OSA1Ljg4NDk0IDE5Ljg0ODcgNS44MDY4MSAyMC4xNjgzIDUuNjUwNTZDMjAuNDkxNSA1LjQ5NDMxIDIwLjc0NzIgNS4yNzQxNCAyMC45MzU0IDQuOTkwMDVDMjEuMTI3MSA0LjcwNTk2IDIxLjIyMyA0LjM3MjE2IDIxLjIyMyAzLjk4ODYzQzIxLjIyMyAzLjUwOTIzIDIxLjA1NjEgMy4xMDI2MiAyMC43MjIzIDIuNzY4ODJDMjAuMzg4NSAyLjQzMTQ2IDE5Ljg1OTQgMi4yNjI3OCAxOS4xMzQ5IDIuMjYyNzhIMTYuNjQyVjUuODg0OTRaIiBmaWxsPSIjRkUwNzA3Ii8+CjxwYXRoIGQ9Ik0xMS44Mzg4IDlMMTEuMzQ2NiA4LjUxNDJMMTMuMzcyOSA2LjQ4NzkySDguMTI0OTlWNS43ODQ4SDEzLjM3MjlMMTEuMzQ2NiAzLjc2NDkxTDExLjgzODggMy4yNzI3MkwxNC43MDI0IDYuMTM2MzZMMTEuODM4OCA5WiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+';
    1718
     
    4344            }
    4445            $this->rag_controller = new S2bAia_AdminRagController();
     46           
     47            if (!class_exists('S2bAia_AdminUsageController')) {
     48                $contr_path = S2BAIA_PATH . "/lib/controllers/AdminUsageController.php";
     49                include_once $contr_path;
     50            }
     51            $this->usage_controller = new S2bAia_AdminUsageController();
    4552
    4653            add_action('admin_menu', array($this, 'registerAdminMenu'));
     
    361368                    'showMainView'
    362369            );
    363 
     370           
     371            $this->registerSafeSubmenu(
     372                    __('Usage', 's2b-ai-assistant'),
     373                    S2BAIA_PREFIX_LOW . 'usage',
     374                    $this->usage_controller,
     375                    'showMainView'
     376            );
     377           
    364378            // Additional menu items if allowed
    365379            if (method_exists('S2bAia_Utils', 'checkEditInstructionAccess') && S2bAia_Utils::checkEditInstructionAccess()) {
  • s2b-ai-assistant/trunk/lib/controllers/ChatBotController.php

    r3338464 r3354820  
    785785                if (S2bAia_AiRequest::testChatGptResponse($response)) {
    786786                    $msg = S2bAia_AiRequest::getChatGptResponseEditMessage($response);
     787                    $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     788                    if($s2baia_use_usage){
     789                        $usage = S2bAia_AiRequest::getUsage($response);
     790                        if (!class_exists('S2bAia_UsageUtils')) {
     791                            $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     792                            include_once $pp;
     793
     794                        }
     795                        $user_id = get_current_user_id();
     796                        $this->load_model('ChatBotModel');
     797                        $model = $this->model;
     798                        $bot = $model->getChatBotSettings($bot_id);
     799                        if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     800                            S2bAia_UsageUtils::recordUsage($data['model'], $user_id, 1, $bot->id, $usage[0] , $usage[1]);
     801                        }
     802                    }
    787803                    $r['result'] = 200;
    788804                    $r['msg'] = wp_kses($msg, S2bAia_Utils::getInstructionAllowedTags());
     
    9921008                    $r['result'] = 200;
    9931009                    $r['msg'] = wp_kses($msg, S2bAia_Utils::getInstructionAllowedTags());
     1010                    $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     1011                    if ($s2baia_use_usage && is_object($response) && isset($response->usage) && is_object($response->usage) && isset($response->usage->prompt_tokens)  && isset($response->usage->completion_tokens) ) {
     1012                       
     1013                        if (!class_exists('S2bAia_UsageUtils')) {
     1014                                    $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     1015                                      include_once $pp;
     1016
     1017                        }
     1018                        $inputtok = $response->usage->prompt_tokens;
     1019                        $outputtok = $response->usage->completion_tokens;
     1020                        $user_id = get_current_user_id();
     1021                            $model = false;
     1022                            $path = S2BAIA_PATH . "/lib/models/ChatBotModel.php";
     1023                            if (file_exists($path)) {
     1024                                $model_name = S2BAIA_CLASS_PREFIX . ucfirst('ChatBotModel');
     1025
     1026                                $model = new $model_name();
     1027                                //ucfirst()
     1028                            }
     1029
     1030                            $bot = $model->getChatBotSettings($bot_id);
     1031                            if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     1032                                S2bAia_UsageUtils::recordUsage(substr($data['model'],0,249), $user_id, 1, $bot->id, $inputtok , $outputtok);
     1033                            }
     1034                    }
    9941035                    //$data['messages'][] = ['role'=>'assistant','content'=>$r['msg']];
    9951036                    $chat_id = $this->getChatId($bot_id);
     
    10931134            }
    10941135
    1095            
     1136            $use_usage = get_option('s2baia_use_usage',0);
    10961137            $response = false;
    10971138            $answer_received = false;
     
    11111152                    }
    11121153                    if (strlen($message_id) > 0) {
    1113                         $response = S2bAia_AiRequest::runAssistant($thread_id, $assistant_id, '');
     1154                        $response = S2bAia_AiRequest::runAssistant($thread_id, $assistant_id, '',$use_usage);
    11141155                        $this->debugLog(wp_json_encode($response), 0, 'runAssistant thread-'.$thread_id.' assistant - '.$assistant_id, $assistant_id, $chat_id);
    11151156                        if (is_array($response) && isset($response['id']) && isset($response['status'])) {
     
    11201161                            $chb_model->updateChat($chat_id, $last_msg, $chat_info, $last_role, $this->chat_session_expired);
    11211162                            $this->updateLog($last_msg, $chat_info, $last_role, $bot_id, $chat_id, 2);
     1163
     1164                            $usage = $response['_usage'] ?? null;
     1165                            if($usage && $use_usage){
     1166                               
     1167                                if (!class_exists('S2bAia_UsageUtils')) {
     1168                                    $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     1169                                    include_once $pp;
     1170
     1171                                }
     1172                                $user_id = get_current_user_id();
     1173                                $this->load_model('ChatBotModel');
     1174                                $model = $this->model;
     1175                                $bot = $model->getChatBotSettings($bot_id);
     1176                                if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     1177                                    $in_tokens  = (int)($usage['prompt_tokens']     ?? 0);
     1178                                    $out_tokens = (int)($usage['completion_tokens'] ?? 0);
     1179
     1180                                    S2bAia_UsageUtils::recordUsage('AI Assistant API Async', $user_id, 1, $bot->id, $in_tokens , $out_tokens);
     1181                                }
     1182                            }
     1183                           
    11221184                            if ($status == 'queued') {
    1123                                 while ($status != 'completed' || $count_loop > 0) {
     1185                                // Use AND here, so the loop stops when completed OR when timeout runs out
     1186                                while ($status !== 'completed' && $count_loop > 0) {
    11241187                                    $response = S2bAia_AiRequest::getRunStepsStatus($thread_id, $run_id);
    11251188                                    $this->debugLog(wp_json_encode($response), 0, 'getRunStepsStatus threaid:'.$thread_id.' runid:'.$run_id, $assistant_id, $chat_id);
     1189
    11261190                                    if (is_array($response) && isset($response['data'])) {
    11271191                                        $list = $response['data'];
    11281192                                        foreach ($list as $ls) {
    1129                                             if ($ls['status'] == 'completed') {
     1193                                            if ($ls['status'] === 'completed') {
     1194                                                // ✅ Steps are completed; now the run should be terminal soon or already
     1195                                                // 1) Fetch final run to get usage
     1196                                                $finalRun = S2bAia_AiRequest::getRun($thread_id, $run_id);
     1197                                                $this->debugLog(wp_json_encode($finalRun), 0, 'finalRun (for usage)', $assistant_id, $chat_id);
     1198                                                $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     1199                                                if($s2baia_use_usage){
     1200                                                    $usage = is_array($finalRun) ? ($finalRun['usage'] ?? null) : null;
     1201                                                    if ($usage && is_array($usage)) {
     1202                                                        // Record usage once per run
     1203                                                        if (!class_exists('S2bAia_UsageUtils')) {
     1204                                                            $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     1205                                                            include_once $pp;
     1206                                                        }
     1207                                                        $user_id = get_current_user_id();
     1208                                                        $this->load_model('ChatBotModel');
     1209                                                        $model = $this->model;
     1210                                                        $bot = $model->getChatBotSettings($bot_id);
     1211
     1212                                                        if (is_object($bot) && !empty($bot->id)) {
     1213                                                            $in_tokens  = (int)($usage['prompt_tokens']     ?? 0);
     1214                                                            $out_tokens = (int)($usage['completion_tokens'] ?? 0);
     1215                                                            S2bAia_UsageUtils::recordUsage('AI Assistant API Async', $user_id, 1, $bot->id, $in_tokens, $out_tokens);
     1216                                                        }
     1217                                                    }
     1218                                                }
     1219                                                // 2) Now fetch messages
    11301220                                                sleep(1);
    11311221                                                $response = S2bAia_AiRequest::listAssistantMessages($thread_id);
     
    11341224                                        }
    11351225                                    } elseif ($response === true) {
    1136                                        
     1226                                        // code path for "steps done" shortcut
     1227                                        // 1) Fetch final run usage here too
     1228                                        $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     1229                                        if($s2baia_use_usage){
     1230                                        $finalRun = S2bAia_AiRequest::getRun($thread_id, $run_id);
     1231                                            $this->debugLog(wp_json_encode($finalRun), 0, 'finalRun (for usage) [shortcut]', $assistant_id, $chat_id);
     1232                                        }
     1233                                        if($s2baia_use_usage){
     1234                                            $usage = is_array($finalRun) ? ($finalRun['usage'] ?? null) : null;
     1235                                            if ($usage && is_array($usage)) {
     1236                                                if (!class_exists('S2bAia_UsageUtils')) {
     1237                                                    $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     1238                                                    include_once $pp;
     1239                                                }
     1240                                                $user_id = get_current_user_id();
     1241                                                $this->load_model('ChatBotModel');
     1242                                                $model = $this->model;
     1243                                                $bot = $model->getChatBotSettings($bot_id);
     1244
     1245                                                if (is_object($bot) && !empty($bot->id)) {
     1246                                                    $in_tokens  = (int)($usage['prompt_tokens']     ?? 0);
     1247                                                    $out_tokens = (int)($usage['completion_tokens'] ?? 0);
     1248                                                    S2bAia_UsageUtils::recordUsage('AI Assistant API Async', $user_id, 1, $bot->id, $in_tokens, $out_tokens);
     1249                                                }
     1250                                            }
     1251                                        }
     1252                                        // 2) Then fetch messages as  already do
    11371253                                        sleep(1);
    11381254                                        $response = S2bAia_AiRequest::listAssistantMessages($thread_id);
    1139                                         $fl2=__DIR__."/response_async_assistant.txt"; 
    1140                                         $logvar = $response ;
    1141                                         //error_log(print_r($logvar,true),3,$fl2);
    11421255                                        $this->debugLog(wp_json_encode($response), 0, 'listAssistantMessages2 threadid:'.$thread_id, $assistant_id, $chat_id);
     1256
    11431257                                        $parsed_response = $this->parseListResponse($response);
    1144                                         if(strlen($parsed_response['text']) > 0 && $parsed_response['text'] !== '**empty' && $parsed_response['text'] !== '**echoed'){
    1145                                             $final_response['msg'] = $parsed_response['text'];
     1258                                        if (strlen($parsed_response['text']) > 0 && $parsed_response['text'] !== '**empty' && $parsed_response['text'] !== '**echoed') {
     1259                                            $final_response['msg']  = $parsed_response['text'];
    11461260                                            $final_response['code'] = 200;
    11471261                                            $answer_received = true;
     
    11501264                                            $this->updateLog($parsed_response['text'], $chat_info, 'assistant', $bot_id, $chat_id, 1);
    11511265                                            break;
    1152                                         }elseif(strlen($parsed_response['text']) > 0  && $parsed_response['text'] == '**echoed'){
     1266                                        } elseif (strlen($parsed_response['text']) > 0 && $parsed_response['text'] == '**echoed') {
    11531267                                            $response = S2bAia_AiRequest::retryRequest();
    11541268                                            $this->debugLog(wp_json_encode($response), 0, 'retryRequest:'.$thread_id, $assistant_id, $chat_id);
    1155                                             if(isset($response) && is_string($response)){
    1156                                                 $final_response['msg'] = $response;
     1269                                            if (isset($response) && is_string($response)) {
     1270                                                $final_response['msg']  = $response;
    11571271                                                $final_response['code'] = 200;
    11581272                                                $answer_received = true;
     
    11631277                                            }
    11641278                                        }
    1165                                         //$logvar = '**empty' ;
    1166                                         //error_log(print_r($logvar,true));
    1167                                     }else{
    1168                                         $final_response['msg'] = esc_html__('Can not establish connection to assistant. Please send your request again.', 's2b-ai-assistant').' '.esc_html__('Error code', 's2b-ai-assistant').':'.'409';
     1279                                    } else {
     1280                                        $final_response['msg']  = esc_html__('Can not establish connection to assistant. Please send your request again.', 's2b-ai-assistant').' '.esc_html__('Error code', 's2b-ai-assistant').':'.'409';
    11691281                                        $final_response['code'] = 409;
    11701282                                    }
     1283
    11711284                                    $count_loop--;
    1172                                    
     1285                                    sleep(1); // gentle pacing
    11731286                                }
    1174                             }else{
    1175                                 $final_response['msg'] = esc_html__('Can not establish connection to assistant. Please send your request again.', 's2b-ai-assistant').' '.esc_html__('Error code', 's2b-ai-assistant').':'.'406';
     1287                            } else {
     1288                                $final_response['msg']  = esc_html__('Can not establish connection to assistant. Please send your request again.', 's2b-ai-assistant').' '.esc_html__('Error code', 's2b-ai-assistant').':'.'406';
    11761289                                $final_response['code'] = 406;
    11771290                            }
     
    13031416                        $debugger = S2bAia_Utils::$global_logger;
    13041417                        $debug_log = (get_option( 's2baia_debug', 0 ) > 0) && is_object($debugger) && strlen(S2bAia_Utils::$log_id) > 0;
    1305 
     1418                       
     1419                        $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     1420                        if($s2baia_use_usage && is_array($response_stream) && count($response_stream) > 3 && is_array($response_stream[3]) && isset($response_stream[3]['prompt_tokens']) && isset($response_stream[3]['completion_tokens'])){
     1421                            $usage = $response_stream[3];
     1422                            if (!class_exists('S2bAia_UsageUtils')) {
     1423                                $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     1424                                include_once $pp;
     1425
     1426                            }
     1427                            $user_id = get_current_user_id();
     1428                            $this->load_model('ChatBotModel');
     1429                            $model = $this->model;
     1430                            $bot = $model->getChatBotSettings($bot_id);
     1431                            if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     1432                                $in_tokens  = $usage['prompt_tokens']     ?? 0;
     1433                                $out_tokens = $usage['completion_tokens'] ?? 0;
     1434                                S2bAia_UsageUtils::recordUsage('AI Assistant API Stream', $user_id, 1, $bot->id, $in_tokens , $out_tokens);
     1435                            }
     1436                        }
    13061437                        //error_log(print_r($logvar,true),3,$fl2);
    13071438                        if(is_array($response_stream) && count($response_stream) > 2){
  • s2b-ai-assistant/trunk/lib/helpers/AiRequest.php

    r3342891 r3354820  
    1515        public static $thread_url = "https://api.openai.com/v1/threads";
    1616        public static $http_client = 'curl';
    17 
     17        public static $default_headers = null;
     18       
    1819        /* prepares chat completion chatGPT API request and calls :sendChatGptRequest
    1920          @param $data     array  received from Edit&Extend metabox form
     
    214215            return '';
    215216        }
     217       
     218        public static function getUsage($response) {
     219            $usage = [0, 0];
     220            if (is_object($response) && isset($response->usage) && is_object($response->usage) && isset($response->usage->prompt_tokens)) {
     221                $usage[0] = $prompt_tokens = $response->usage->prompt_tokens;
     222                $usage[1] = $completion_tokens = $response->usage->completion_tokens; //completion_tokens
     223               
     224            }
     225            return $usage;
     226        }
     227
    216228
    217229        public static function createAssistantRetrievalV2($options) {
     
    671683        }
    672684
    673         public static function runAssistant($thread_id, $assistant_id, $instruction) {
     685        public static function runAssistantV1($thread_id, $assistant_id, $instruction) {
    674686
    675687            $url = self::$thread_url . "/" . $thread_id . "/runs";
     
    721733        }
    722734
     735        private static function oa_headers($api_key = null) {
     736            $key = $api_key ?: (self::$gpt_key ?: get_option(S2BAIA_PREFIX_LOW . 'open_ai_gpt_key', ''));
     737            return [
     738                'Content-Type' => 'application/json',
     739                'Authorization' => 'Bearer ' . $key,
     740                'OpenAI-Beta'  => 'assistants=v2',
     741            ];
     742        }
     743
     744        // Small helper: fetch a single run
     745        public static function getRun($thread_id, $run_id, $api_key = null) {
     746            $url = "https://api.openai.com/v1/threads/{$thread_id}/runs/{$run_id}";
     747            $resp = wp_remote_get($url, [
     748                'headers' => self::oa_headers($api_key),
     749                'timeout' => 60,
     750            ]);
     751            if (is_wp_error($resp)) {
     752                return ['error' => $resp->get_error_message()];
     753            }
     754            return json_decode(wp_remote_retrieve_body($resp), true);
     755        }
     756
     757        // Wait/poll until run reaches a terminal state or timeout
     758        private static function waitForRunUsage($thread_id, $run_id, $api_key = null, $max_wait_sec = 60) {
     759            $started   = time();
     760            $sleep     = 0.6;  // start interval
     761            $max_sleep = 2.5;  // cap interval
     762
     763            while (true) {
     764                $run = self::getRun($thread_id, $run_id, $api_key);
     765                if (!is_array($run)) {
     766                    return ['error' => 'Unexpected run payload'];
     767                }
     768                $status = $run['status'] ?? 'unknown';
     769
     770                // Terminal states
     771                if (in_array($status, ['completed', 'failed', 'cancelled', 'expired'], true)) {
     772                    return [
     773                        'status' => $status,
     774                        'usage'  => $run['usage'] ?? null,
     775                        'model'  => $run['model'] ?? null,
     776                        'run'    => $run,
     777                    ];
     778                }
     779
     780                // Timeout?
     781                if ((time() - $started) >= $max_wait_sec) {
     782                    return [
     783                        'status' => $status,
     784                        'usage'  => $run['usage'] ?? null,
     785                        'model'  => $run['model'] ?? null,
     786                        'timeout' => true,
     787                        'run'    => $run,
     788                    ];
     789                }
     790
     791                // Backoff then continue
     792                usleep((int)($sleep * 1000));
     793                $sleep = min($max_sleep, $sleep * 1.4);
     794            }
     795        }
     796       
     797        public static function runAssistant($thread_id, $assistant_id, $instruction, $collect_usage = true, $max_wait_sec = 60) {
     798
     799            $url = self::$thread_url . "/" . $thread_id . "/runs";
     800            if (strlen(self::$gpt_key) == 0) {
     801                self::$gpt_key = get_option(S2BAIA_PREFIX_LOW . 'open_ai_gpt_key', '');
     802            }
     803
     804            $data = (strlen($instruction) > 1)
     805                ? ["assistant_id" => $assistant_id, "instructions" => $instruction]
     806                : ["assistant_id" => $assistant_id];
     807
     808            $headers  = self::oa_headers(self::$gpt_key);
     809
     810            $response = wp_remote_request($url, [
     811                'method'  => 'POST',
     812                'headers' => $headers,
     813                'body'    => wp_json_encode($data),
     814                'timeout' => 120,
     815            ]);
     816
     817            $res = [];
     818
     819            if (is_wp_error($response)) {
     820                $res['error_msg'] = $response->get_error_message();
     821                $res['code']      = 500;
     822                return $res;
     823            }
     824
     825            $res['code'] = wp_remote_retrieve_response_code($response);
     826            $res['body'] = wp_remote_retrieve_body($response);
     827
     828            if (!strlen($res['body'])) {
     829                return $res;
     830            }
     831
     832            $res_obj = json_decode($res['body'], true);
     833            // Return early if caller doesn't want blocking usage calc
     834            if (!$collect_usage) {
     835                // Add convenience fields without changing old shape
     836                if (is_array($res_obj)) {
     837                    $res_obj['_run_id']   = $res_obj['id']    ?? null;
     838                    $res_obj['_status']   = $res_obj['status']?? null;
     839                    $res_obj['_usage']    = $res_obj['usage'] ?? null; // rarely present at creation
     840                }
     841                return $res_obj;
     842            }
     843
     844            // Collect usage: poll until completed (or timeout)
     845            $run_id = $res_obj['id'] ?? null;
     846            if (!$run_id) {
     847                // Unexpected; return original payload
     848                $res_obj['_usage_error'] = 'Missing run_id in creation response';
     849                return $res_obj;
     850            }
     851
     852            $final = self::waitForRunUsage($thread_id, $run_id, self::$gpt_key, $max_wait_sec);
     853
     854            // Attach usage + status back to the original object (non-breaking)
     855            $res_obj['_run_id']   = $run_id;
     856            $res_obj['id']   = $run_id;
     857            $res_obj['_status']   = $final['status'] ?? null;
     858            $res_obj['_usage']    = $final['usage']  ?? null;       // ['prompt_tokens','completion_tokens','total_tokens']
     859            $res_obj['_model']    = $final['model']  ?? null;
     860            $res_obj['_timeout']  = $final['timeout']?? false;
     861
     862            return $res_obj;
     863        }
     864
     865       
    723866        public static function uploadFile($file_path) {
    724867            $http_client = self::$http_client;
     
    9091052                require_once S2BAIA_PATH . '/lib/helpers/CurlClient.php';
    9101053            }
    911             $response = S2bAia_CurlClient::openStream($url, $options);
     1054            $response = S2bAia_CurlClient::openStream($url, $options,$thread_id);
    9121055
    9131056            if ($response === FALSE) {
     
    9581101            $args_f = apply_filters( 's2baia_chat_assistant_stream_filter_args', $args, $thread_id, $assistant_id, $instruction );
    9591102            // Call the WordPress-based openStream function
    960             return S2bAia_WpHttpClient::openStream($url_f, $args_f);
     1103            return S2bAia_WpHttpClient::openStream($url_f, $args_f,$thread_id);
    9611104        }
    9621105    }
  • s2b-ai-assistant/trunk/lib/helpers/UpdateUtils.php

    r3292097 r3354820  
    3636            if($current_db_version < 7){
    3737                self::version7();
    38                 update_option('s2baia_config_pinecone_system_message', esc_html__('Answer the question based on the context below','s2b-ai-assistant'));
     38                update_option('s2baia_config_pinecone_system_message', 'Answer the question based on the context below','s2b-ai-assistant');
    3939                update_option('s2baia_database_version', 7);
    4040            }
     
    5555            }
    5656           
     57            if($current_db_version < 11){
     58                self::version11();
     59                update_option('s2baia_database_version', 11);
     60            }
    5761        }
    5862       
     
    831835                'chat_height_metrics'=>'%',
    832836                'greeting_message' => 0,
    833                 'greeting_message_text'=>esc_html__('Hello! I am an AI Assistant. How can I help you?','s2b-ai-assistant'),
    834                 'message_placeholder'=>esc_html__('Ctrl+Enter to send request','s2b-ai-assistant'),
     837                'greeting_message_text'=>'Hello! I am an AI Assistant. How can I help you?',
     838                'message_placeholder'=>'Ctrl+Enter to send request',
    835839                'chatbot_name'=>'GPT Assistant',
    836840                'compliance_text'=>'',
     
    854858                'access_for_guests'=> 1,
    855859                'chatbot_picture_url' => '',
    856                 'send_button_text' => esc_html__('Send','s2b-ai-assistant'),
    857                 'clear_button_text' => esc_html__('Clear','s2b-ai-assistant')
     860                'send_button_text' => 'Send',
     861                'clear_button_text' => 'Clear'
    858862                ];
    859863                   
     
    9981002        }
    9991003       
    1000        
     1004        /*public static function version11() {
     1005            global $wpdb;
     1006
     1007            $table_name = $wpdb->prefix . 's2baia_usage';
     1008            $charset_collate = $wpdb->get_charset_collate();
     1009
     1010            $sql = "CREATE TABLE  IF NOT EXISTS  {$table_name} (
     1011          id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
     1012          type_of_resource SMALLINT(5) UNSIGNED NOT NULL DEFAULT 1,
     1013          id_resource VARCHAR(191) NOT NULL,
     1014          id_user BIGINT(20) UNSIGNED NOT NULL,
     1015          resource_session_id VARCHAR(191) NOT NULL DEFAULT '',
     1016          model VARCHAR(191) NOT NULL DEFAULT '',
     1017          date_updated DATE DEFAULT NULL,
     1018          time_updated INT(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'seconds within date',
     1019          input_tokens BIGINT(20) UNSIGNED DEFAULT NULL,
     1020          output_tokens BIGINT(20) UNSIGNED DEFAULT NULL,
     1021          details LONGTEXT,
     1022          PRIMARY KEY (id),
     1023
     1024          KEY type_resource_user (
     1025            type_of_resource,
     1026            id_resource(120),
     1027            id_user,
     1028            model(60)
     1029          ),
     1030          KEY type_resource_user_session (
     1031            type_of_resource,
     1032            id_resource(120),
     1033            id_user,
     1034            resource_session_id(60)
     1035          ),
     1036          KEY type_resource_user_date (
     1037            type_of_resource,
     1038            id_resource(120),
     1039            id_user,
     1040            model(60),
     1041            date_updated
     1042          )
     1043        ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC $charset_collate;";
     1044
     1045
     1046            require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     1047            dbDelta($sql);
     1048        }*/
     1049   
     1050    public static function version11() {
     1051        self::create_usage_table();
     1052        update_option('s2baia_database_version', 11);
     1053        self::ensure_usage_indexes();
     1054    }
     1055   
     1056    private static function create_usage_table() {
     1057        global $wpdb;
     1058        $table = $wpdb->prefix . 's2baia_usage';
     1059        $charset_collate = $wpdb->get_charset_collate();
     1060        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     1061
     1062        // Base table only: columns + PK (no secondary keys yet)
     1063        $sql = "CREATE TABLE  IF NOT EXISTS  {$table} (
     1064            id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
     1065            type_of_resource SMALLINT(5) UNSIGNED NOT NULL DEFAULT 1,
     1066            id_resource VARCHAR(191) NOT NULL,
     1067            id_user BIGINT(20) UNSIGNED NOT NULL,
     1068            resource_session_id VARCHAR(191) NOT NULL DEFAULT '',
     1069            model VARCHAR(191) NOT NULL DEFAULT '',
     1070            date_updated DATE DEFAULT NULL,
     1071            time_updated INT(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'seconds within date',
     1072            input_tokens BIGINT(20) UNSIGNED DEFAULT NULL,
     1073            output_tokens BIGINT(20) UNSIGNED DEFAULT NULL,
     1074            details LONGTEXT,
     1075            PRIMARY KEY (id)
     1076        ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC {$charset_collate};";
     1077
     1078        $wpdb->suppress_errors(false);
     1079        dbDelta($sql);
     1080
     1081        if (!empty($wpdb->last_error)) {
     1082            //error_log('[s2baia] CREATE TABLE error: ' . $wpdb->last_error);
     1083        }
     1084        }
     1085
     1086    private static function ensure_usage_indexes() {
     1087        global $wpdb;
     1088        $table = $wpdb->prefix . 's2baia_usage';
     1089
     1090        // Desired indexes as arrays of [col, optional_prefix]
     1091        // We’ll try full columns first; on failure we retry with prefixes that are 1000-byte safe.
     1092        $wanted = [
     1093            'type_resource_user' => [
     1094                ['type_of_resource', null],
     1095                ['id_resource', null], // may get prefixed if key-too-long
     1096                ['id_user', null],
     1097                ['model', null],       // may get prefixed if key-too-long
     1098            ],
     1099            'type_resource_user_session' => [
     1100                ['type_of_resource', null],
     1101                ['id_resource', null],       // prefixed fallback
     1102                ['id_user', null],
     1103                ['resource_session_id', null], // prefixed fallback
     1104            ],
     1105            'type_resource_user_date' => [
     1106                ['type_of_resource', null],
     1107                ['id_resource', null], // prefixed fallback
     1108                ['id_user', null],
     1109                ['model', null],       // prefixed fallback
     1110                ['date_updated', null],
     1111            ],
     1112        ];
     1113
     1114        foreach ($wanted as $indexName => $cols) {
     1115            self::maybe_add_index($table, $indexName, $cols);
     1116        }
    10011117    }
    10021118
     1119    private static function usage_index_exists($indexName) {
     1120        global $wpdb;
     1121        return (bool) $wpdb->get_var(/* phpcs:ignore WordPress.DB.DirectDatabaseQuery */
     1122            $wpdb->prepare("SHOW INDEX FROM {$wpdb->prefix}s2baia_usage WHERE Key_name = %s", $indexName)
     1123        );
     1124    }
     1125
     1126    /**
     1127     * Backtick-quote a MySQL identifier after strict validation.
     1128     */
     1129    private static function quote_ident(string $name): string {
     1130        // allow only letters, numbers, underscore, and $
     1131        if (!preg_match('/^[A-Za-z0-9_\$]+$/', $name)) {
     1132        throw new InvalidArgumentException("Invalid identifier");
     1133        }
     1134        return '`' . $name . '`';
     1135    }
     1136
     1137    /**
     1138     * Add an INDEX with optional prefix lengths (for text columns).
     1139     * $colsSpec is an array of [columnName, prefixLen|null]
     1140     */
     1141    private static function try_add_index($table, $indexName, $colsSpec) {
     1142        global $wpdb;
     1143
     1144        // optional: hard whitelist of columns that exist in this table
     1145        static $allowed_cols = [
     1146        'type_of_resource', 'id_resource', 'id_user',
     1147        'resource_session_id', 'model', 'date_updated'
     1148        ];
     1149
     1150        $parts = [];
     1151        foreach ($colsSpec as [$col, $prefix]) {
     1152        if (!in_array($col, $allowed_cols, true)) {
     1153            throw new InvalidArgumentException("Unknown column for index");
     1154        }
     1155        $colSql = self::quote_ident($col) . ($prefix ? '(' . (int)$prefix . ')' : '');
     1156        $parts[] = $colSql;
     1157        }
     1158
     1159        $tableSql = self::quote_ident($table);
     1160        $indexSql = self::quote_ident($indexName);
     1161        $colsSql  = implode(', ', $parts);
     1162        $sql = "ALTER TABLE {$tableSql} ADD INDEX {$indexSql} ({$colsSql})";
     1163
     1164        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifiers cannot be parameterized; validated & quoted above.
     1165        $wpdb->query($sql);
     1166
     1167        return empty($wpdb->last_error);
     1168    }
     1169
     1170
     1171    private static function maybe_add_index($table, $indexName, $cols) {
     1172   
     1173        global $wpdb;
     1174        if (self::usage_index_exists($indexName)) return;
     1175
     1176        $wpdb->suppress_errors(false);
     1177
     1178        // 1) Try full-length first.
     1179        if (self::try_add_index($table, $indexName, $cols)) return;
     1180
     1181        $err1 = $wpdb->last_error;
     1182        // 2) If key-too-long (or anything else), retry with safe prefixes for text columns under utf8mb4.
     1183        //    Rule: keep total <= 1000 bytes on older hosts. Use conservative (120, 60) prefixes.
     1184        $bytesPerChar = (stripos($wpdb->charset, 'utf8mb4') !== false) ? 4 : 3;
     1185
     1186        $prefixed = [];
     1187        foreach ($cols as [$col, $prefix]) {
     1188            if ($prefix !== null) { // user-forced prefix (not used above, but kept flexible)
     1189                $prefixed[] = [$col, $prefix];
     1190                continue;
     1191            }
     1192            // Heuristic: prefix VARCHAR/TEXT-like columns, not numeric/date types.
     1193            // Ask MySQL about the column type:
     1194            $type = $wpdb->get_var($wpdb->prepare(
     1195                "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS
     1196                 WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = %s",
     1197                $table, $col
     1198            ));
     1199            $is_texty = in_array(strtolower((string)$type), ['varchar','text','mediumtext','longtext','tinytext']);
     1200
     1201            if ($is_texty) {
     1202                // conservative default prefixes for composites under legacy 1000-byte limit
     1203                // 120 chars ≈ 480 bytes on utf8mb4
     1204                $prefixed[] = [$col, 120];
     1205            } else {
     1206                $prefixed[] = [$col, null];
     1207            }
     1208        }
     1209
     1210        // Refine: if multiple text columns, make the later ones smaller (e.g., 60)
     1211        $textIdx = 0;
     1212        foreach ($prefixed as $i => [$col, $prefix]) {
     1213            if ($prefix) {
     1214                $prefixed[$i][1] = ($textIdx === 0) ? 120 : 60; // first text col 120, next ones 60
     1215                $textIdx++;
     1216            }
     1217        }
     1218
     1219        // Try again with prefixes
     1220        $wpdb->last_error = '';
     1221        if (self::try_add_index($table, $indexName, $prefixed)) {
     1222            return;
     1223        }
     1224
     1225        /*error_log(sprintf(
     1226            '[s2baia] Failed to add index %s to %s. First error: %s ; Prefixed attempt error: %s ; Attempted spec: %s',
     1227            $indexName, $table, $err1, $wpdb->last_error, json_encode($prefixed)
     1228        ));*/
     1229    }
     1230
     1231       
     1232    }
     1233
    10031234}
  • s2b-ai-assistant/trunk/lib/helpers/WpHttpClient.php

    r3292097 r3354820  
    1010        public static $annotations = [];
    1111
     12        public static $run_id = null;
     13        public static $run_usage = null;
     14
    1215        public static function sendWpRequest($url, $args) {
    1316            $response = wp_remote_request($url, $args);
     
    3033            if (!empty($data)) {
    3134                $obj = json_decode($data, true);
    32 
     35                if (!$obj) {
     36                    return $data; // keep old behavior
     37                }
     38                // --- NEW: unwrap Assistants v2 SSE envelope if present ---------------
     39                // Many lines look like: {"event":"thread.run.created","data":{...}}
     40                $event   = $obj['event'] ?? null;
     41                $payload = $obj['data']  ?? null;
     42                if ($event && is_array($payload)) {
     43                    // Capture run_id / usage from run lifecycle events
     44                    switch ($event) {
     45                        case 'thread.run.created':
     46                        case 'thread.run.in_progress':
     47                        case 'thread.run.queued':
     48                        case 'thread.run.requires_action':
     49                        case 'thread.run.completed':
     50                        case 'thread.run.failed':
     51                        case 'thread.run.cancelled':
     52                        case 'thread.run.expired':
     53                            if (!empty($payload['id'])) {
     54                                self::$run_id = $payload['id'];
     55                            }
     56                            if (!empty($payload['usage']) && is_array($payload['usage'])) {
     57                                // usage = { prompt_tokens, completion_tokens, total_tokens }
     58                                self::$run_usage = $payload['usage'];
     59                            }
     60                            break;
     61                    }
     62
     63                    // For downstream legacy logic, operate on the inner payload
     64                    $obj = $payload;
     65                }
     66
     67                // --- NEW: also catch bare run objects (in case stream sends them) ----
     68                if (($obj['object'] ?? '') === 'thread.run') {
     69                    if (!empty($obj['id'])) {
     70                        self::$run_id = $obj['id'];
     71                    }
     72                    if (!empty($obj['usage']) && is_array($obj['usage'])) {
     73                        self::$run_usage = $obj['usage'];
     74                    }
     75                }
     76               
     77               
     78               
    3379                if (!class_exists('S2bAia_FileExtender')) {
    3480                    require_once S2BAIA_PATH . '/lib/classes/FileExtender.php';
     
    145191       
    146192
    147         public static function openStream($url, $args) {
     193        public static function openStreamFast($url, $args) {
    148194            self::$stream_answer = ''; // Reset buffer
     195            //
    149196            // Make request
    150197            $response = wp_remote_request($url, $args);
     
    166213            return [self::$stream_answer, self::$annotations, self::$thread_message]; // Return final accumulated response
    167214        }
     215       
     216        public static function openStream($url, $args,  $thread_id) {
     217            self::$stream_answer  = '';
     218            self::$annotations    = [];
     219            self::$thread_message = null;
     220            self::$run_id         = null;
     221            self::$run_usage      = null;
     222
     223            $response = wp_remote_request($url, $args);
     224            if (is_wp_error($response)) {
     225                return [["error" => $response->get_error_message()], false];
     226            }
     227
     228            $body  = wp_remote_retrieve_body($response);
     229            $lines = explode("\n", $body);
     230
     231            foreach ($lines as $line) {
     232                if (strpos($line, 'data:') === 0) {
     233                    $data = trim(substr($line, strlen('data:')));
     234                    self::handleEvent($data); // will fill answer + run_id + usage
     235                }
     236            }
     237           
     238            // If usage wasn't present in the stream, fetch it from the final run
     239            if (!self::$run_usage && self::$run_id) {
     240                $api_key = get_option(S2BAIA_PREFIX_LOW . 'open_ai_gpt_key', '');
     241                $usage = self::fetchRunUsage($thread_id, self::$run_id, $api_key);
     242                if ($usage) {
     243                    self::$run_usage = $usage;
     244                }
     245            }
     246
     247            // Return also the usage + run_id so you can record cost
     248            return [self::$stream_answer, self::$annotations, self::$thread_message, self::$run_usage, self::$run_id];
     249        }
     250       
     251        // Separate GET to fetch run usage if the stream didn’t include it
     252        public static function fetchRunUsage($thread_id, $run_id, $api_key) {
     253            $url = "https://api.openai.com/v1/threads/$thread_id/runs/$run_id";
     254            $resp = wp_remote_get($url, [
     255                'headers' => [
     256                    'Authorization' => 'Bearer ' . $api_key,
     257                    'OpenAI-Beta'   => 'assistants=v2', // keep if you’re on Assistants v2
     258                    'Content-Type'  => 'application/json',
     259                ],
     260                'timeout' => 60,
     261            ]);
     262
     263            if (is_wp_error($resp)) return null;
     264
     265            $body = json_decode(wp_remote_retrieve_body($resp), true);
     266            return $body['usage'] ?? null;
     267        }
    168268
    169269        public static function generateDownloadLink($file_id) {
  • s2b-ai-assistant/trunk/lib/integrations/anthropic/Anthropic.php

    r3338465 r3354820  
    297297           
    298298            if(empty($provider) || $provider != 'anthropic'){
     299                if(is_array($result) && isset($result['msg']) && isset($result['code']) && isset($result['result']) ){
     300                    return $result;
     301                }
    299302                return ['msg'=>'','code'=>404,'result'=>404];
    300303            }
     
    585588        //$returned_model = $decoded_response['model'];
    586589        //$usage = $decoded_response['usage'];
     590        $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     591       
     592        if($s2baia_use_usage  && is_array($decoded_response) && isset($decoded_response['usage'])){
     593            $usage = $decoded_response['usage'];//$selected_model
     594            $intok = 0;
     595            $outok = 0;
     596            if(is_array($usage) && isset($usage['input_tokens']) ){
     597                $intok = (int)$usage['input_tokens'];
     598            }   
     599            if(is_array($usage) && isset($usage['output_tokens']) ){// 
     600                $outok = (int)$usage['output_tokens'];
     601            }   
     602            if (!class_exists('S2bAia_UsageUtils')) {
     603                        $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     604                        include_once $pp;
     605
     606            }
     607            $user_id = get_current_user_id();
     608            $model = false;
     609            $path = S2BAIA_PATH . "/lib/models/ChatBotModel.php";
     610            if (file_exists($path)) {
     611                $model_name = S2BAIA_CLASS_PREFIX . ucfirst('ChatBotModel');
     612
     613                $model = new $model_name();
     614                //ucfirst()
     615            }
     616
     617            $bot = $model->getChatBotSettings($bot_id);
     618            if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     619                S2bAia_UsageUtils::recordUsage(substr($selected_model,0,249), $user_id, 1, $bot->id, $intok , $outok);
     620            }
     621           
     622        }
    587623        $text_content = '';
    588624        if (isset($decoded_response['content'])) {
  • s2b-ai-assistant/trunk/lib/integrations/deepseek/DeepSeek.php

    r3338464 r3354820  
    251251           
    252252            if(empty($provider) || $provider != 'deepseek'){
     253                if(is_array($result) && isset($result['msg']) && isset($result['code']) && isset($result['result']) ){
     254                    return $result;
     255                }
    253256                return ['msg'=>'','code'=>404,'result'=>404];
    254257            }
     
    516519        $response_body = wp_remote_retrieve_body($response);
    517520        $decoded_response = json_decode($response_body, true);
    518 
     521        $s2baia_use_usage = get_option('s2baia_use_usage', 0);
     522        if ($s2baia_use_usage && isset($decoded_response['usage'])) {
     523            if (is_array($decoded_response['usage']) && isset($decoded_response['usage']['prompt_tokens']) && isset($decoded_response['usage']['completion_tokens'])) {
     524                if (!class_exists('S2bAia_UsageUtils')) {
     525                            $pp = S2BAIA_PATH . '/lib/helpers/UsageUtils.php';
     526                            include_once $pp;
     527
     528                        }
     529                        $user_id = get_current_user_id();
     530                        $model = false;
     531                        $path = S2BAIA_PATH . "/lib/models/ChatBotModel.php";
     532                        if (file_exists($path)) {
     533                            $model_name = S2BAIA_CLASS_PREFIX . ucfirst('ChatBotModel');
     534                            $model = new $model_name();
     535                            //ucfirst()
     536                        }
     537
     538                        $bot = $model->getChatBotSettings($bot_id);
     539                        if(is_object($bot) && isset($bot->id) && $bot->id > 0){
     540                            S2bAia_UsageUtils::recordUsage($selected_model, $user_id, 1, $bot->id, $decoded_response['usage']['prompt_tokens'] , $decoded_response['usage']['completion_tokens']);
     541                        }
     542            }
     543        }
    519544        if (isset($decoded_response['choices'][0]['message']['content'])) {
    520545            return  [
  • s2b-ai-assistant/trunk/readme.txt

    r3342891 r3354820  
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    10 Stable tag: 1.7.5
     10Stable tag: 1.7.6
    1111
    1212Create multiple AI chatbots with OpenAI, Claude, xAI, DeepSeek models with different styles and behavior, content aware features   ...
     
    266266== Changelog ==
    267267
     268= 1.7.6 =
     269* Fix integrations conflict. Add tokens usage statistic
     270
    268271= 1.7.5 =
    269272* Quick fix to add basic GPT-5 model support. Only chat completion API. Full support is coming.
  • s2b-ai-assistant/trunk/s2b-ai-assistant.php

    r3342891 r3354820  
    88  Text Domain: s2b-ai-assistant
    99  Domain Path: /lang
    10   Version: 1.7.5
     10  Version: 1.7.6
    1111  License:  GPL-2.0+
    1212  License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
     
    4343define( 'S2BAIA_CHATGPT_BOT_PREFIX', 's2baia_chatbot_' );
    4444define( 'S2BAIA_CHATGPT_BOT_OPTIONS_PREFIX', 's2baia_chatbot_opt_' );
    45 define('S2BAIA_VERSION', '1.7.4');
     45define('S2BAIA_VERSION', '1.7.6');
    4646//Init the plugin
    4747require_once S2BAIA_PATH . '/lib/helpers/Utils.php';
  • s2b-ai-assistant/trunk/views/backend/chatbot/chatbot_general.php

    r3338464 r3354820  
    774774                                    </div>
    775775                                </div>
     776                               
     777                               
     778                                <div class="s2baia_block_content" >
     779                                    <div class="s2baia_row_header">
     780                                        <label for="s2baia_chatbot_use_usage">
     781                                            <?php esc_html_e('Log Usage', 's2b-ai-assistant'); ?>:
     782                                        </label>
     783                                    </div>
     784                                    <div  class="s2baia_row_content s2baia_pr">
     785                                        <div  style="position:relative;">
     786                                            <?php
     787                                            $checked = '';
     788                                            $usage_used = get_option('s2baia_use_usage', 0);
     789                                            if ($usage_used == 1) {
     790                                                    $checked = ' checked ';
     791                                                }
     792                                            ?>
     793                                           
     794                                            <input type="checkbox" id="s2baia_chatbot_use_usage"
     795                                                   name="s2baia_chatbot_use_usage"
     796                                                       <?php echo esc_html($checked); ?>  >
     797
     798                                        </div>
     799                                        <p class="s2baia_input_description">
     800                                            <span style="display: inline;">
     801                                                <?php esc_html_e('Check this if you want to collect statistic of tokens usage by chatbots', 's2b-ai-assistant'); ?>
     802                                            </span>
     803                                        </p>
     804                                    </div>
     805                                </div>
    776806                                <?php
    777807                                }
Note: See TracChangeset for help on using the changeset viewer.