PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.2
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / modules / discussions.php
ai-engine / classes / modules Last commit date
advisor.php 2 years ago chatbot.php 1 year ago discussions.php 1 year ago files.php 1 year ago gdpr.php 1 year ago security.php 1 year ago tasks.php 1 year ago wand.php 1 year ago
discussions.php
654 lines
1 <?php
2
3 class Meow_MWAI_Modules_Discussions {
4 private $wpdb = null;
5 private $core = null;
6 public $table_chats = null;
7 private $db_check = false;
8 private $namespace_admin = 'mwai/v1';
9 private $namespace_ui = 'mwai-ui/v1';
10
11 public function __construct() {
12 global $wpdb;
13 $this->wpdb = $wpdb;
14 global $mwai_core;
15 $this->core = $mwai_core;
16 $this->table_chats = $wpdb->prefix . 'mwai_chats';
17
18 if ( $this->core->get_option( 'chatbot_discussions' ) ) {
19 add_filter( 'mwai_chatbot_reply', [ $this, 'chatbot_reply' ], 10, 4 );
20 add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
21
22 if ( ! wp_next_scheduled( 'mwai_discussions' ) ) {
23 wp_schedule_event( time(), 'hourly', 'mwai_discussions' );
24 }
25 add_action( 'mwai_discussions', [ $this, 'cron_discussions' ] );
26 }
27 }
28
29 public function rest_api_init() {
30 // Admin
31 register_rest_route( $this->namespace_admin, '/discussions/list', [
32 'methods' => 'POST',
33 'callback' => [ $this, 'rest_discussions_list' ],
34 'permission_callback' => [ $this->core, 'can_access_settings' ],
35 ] );
36 register_rest_route( $this->namespace_admin, '/discussions/delete', [
37 'methods' => 'POST',
38 'callback' => [ $this, 'rest_discussions_delete_admin' ],
39 'permission_callback' => [ $this->core, 'can_access_settings' ],
40 ] );
41
42 // UI
43 register_rest_route( $this->namespace_ui, '/discussions/list', [
44 'methods' => 'POST',
45 'callback' => [ $this, 'rest_discussions_ui_list' ],
46 'permission_callback' => '__return_true'
47 ] );
48 register_rest_route( $this->namespace_ui, '/discussions/edit', [
49 'methods' => 'POST',
50 'callback' => [ $this, 'rest_discussions_ui_edit' ],
51 'permission_callback' => '__return_true'
52 ] );
53 register_rest_route( $this->namespace_ui, '/discussions/delete', [
54 'methods' => 'POST',
55 'callback' => [ $this, 'rest_discussions_delete' ],
56 'permission_callback' => [ $this, 'can_delete_discussion' ],
57 ] );
58 }
59
60 function can_delete_discussion( $request ) {
61 $params = $request->get_json_params();
62 $chatIds = isset( $params['chatIds'] ) ? $params['chatIds'] : null;
63 $userId = get_current_user_id();
64 if ( ! $userId ) {
65 return false;
66 }
67 foreach ( $chatIds as $chatId ) {
68 $chat = $this->wpdb->get_row(
69 $this->wpdb->prepare(
70 "SELECT * FROM $this->table_chats WHERE chatId = %s",
71 $chatId
72 )
73 );
74 if ( ! $chat || (int) $chat->userId !== (int) $userId ) {
75 return false;
76 }
77 }
78 return true;
79 }
80
81 /**
82 * Generate or update the title for a specific discussion
83 * by calling the AI (if it meets the requirements).
84 *
85 * @param stdClass $discussion A row from the DB (object form).
86 * @return void
87 */
88 private function generate_title_for_discussion( $discussion ) {
89 // Check if there's already a title
90 if ( ! empty( $discussion->title ) ) {
91 return; // Nothing to do if title is already set.
92 }
93
94 // Ensure it's not older than 10 days, or whatever logic you prefer
95 $ten_days_ago = strtotime( '-10 days' );
96 if ( strtotime( $discussion->updated ) < $ten_days_ago ) {
97 return; // Skip if older than 10 days
98 }
99
100 // We expect JSON in the messages
101 $messages = json_decode( $discussion->messages, true );
102 if ( ! is_array( $messages ) ) {
103 return;
104 }
105
106 // Check for at least one user and one assistant message
107 $has_user_message = false;
108 $has_assistant_message = false;
109 foreach ( $messages as $message ) {
110 if ( isset( $message['role'] ) ) {
111 if ( $message['role'] === 'user' ) {
112 $has_user_message = true;
113 }
114 if ( $message['role'] === 'assistant' ) {
115 $has_assistant_message = true;
116 }
117 }
118 if ( $has_user_message && $has_assistant_message ) {
119 break;
120 }
121 }
122
123 if ( ! ( $has_user_message && $has_assistant_message ) ) {
124 return; // If doesn't have both, skip
125 }
126
127 // Prepare the conversation text for the prompt
128 $conversation_text = '';
129 foreach ( $messages as $message ) {
130 if ( isset( $message['role'] ) && isset( $message['content'] ) ) {
131 $role = ucfirst( $message['role'] );
132 $content = $message['content'];
133 $conversation_text .= "$role: $content\n";
134 }
135 }
136
137 $base_prompt = "Based on the following conversation, generate a concise and specific title for the discussion, strictly less than 64 characters. Focus on the main topic, avoiding unnecessary words such as articles, pronouns, or adjectives. Do not include any punctuation at the end. Do not include anything else than the title itself, only one sentence, no line breaks, just the title.\n\nConversation:\n$conversation_text\n";
138 $prompt = apply_filters( 'mwai_discussions_title_prompt', $base_prompt, $conversation_text, $discussion );
139
140 // Run the AI query using the fast environment
141 global $mwai;
142 $answer = $mwai->simpleTextQuery( $prompt, [
143 'scope' => 'discussions',
144 'envId' => $this->core->get_option( 'ai_fast_default_env' ),
145 'model' => $this->core->get_option( 'ai_fast_default_model' ),
146 ] );
147
148 // Clean up the answer
149 $title = trim( $answer );
150 $title = rtrim( $title, ".!?:;,—–-–" ); // Remove trailing punctuation
151 $title = substr( $title, 0, 64 ); // Ensure less than 64 characters
152 if ( empty( $title ) ) {
153 $title = 'Untitled';
154 }
155
156 // Update the discussion with the title
157 $updated = $this->wpdb->update(
158 $this->table_chats,
159 [ 'title' => $title ],
160 [ 'id' => $discussion->id ]
161 );
162 if ( $updated === false ) {
163 error_log( "Failed to update the title for discussion ID {$discussion->id}" );
164 }
165 }
166
167
168 /**
169 * Admin route for listing discussions. No forced logic here.
170 */
171 public function rest_discussions_list( $request ) {
172 try {
173 $params = $request->get_json_params();
174 $offset = $params['offset'];
175 $limit = $params['limit'];
176 $filters = $params['filters'];
177 $sort = $params['sort'];
178
179 // Retrieve the chats
180 $chats = $this->chats_query( [], $offset, $limit, $filters, $sort );
181
182 return new WP_REST_Response( [ 'success' => true, 'total' => $chats['total'], 'chats' => $chats['rows'] ], 200 );
183 }
184 catch( Exception $e ) {
185 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
186 }
187 }
188
189 public function rest_discussions_ui_edit( $request ) {
190 try {
191 $params = $request->get_json_params();
192 $chatId = isset( $params['chatId'] ) ? sanitize_text_field( $params['chatId'] ) : null;
193 $title = isset( $params['title'] ) ? sanitize_text_field( $params['title'] ) : null;
194
195 if ( is_null( $chatId ) || is_null( $title ) ) {
196 return new WP_REST_Response( [ 'success' => false, 'message' => 'chatId and title are required.' ], 400 );
197 }
198
199 $userId = get_current_user_id();
200 if ( ! $userId ) {
201 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be logged in.' ], 401 );
202 }
203
204 // Update the discussion title for the current user
205 $updated = $this->wpdb->update(
206 $this->table_chats,
207 [ 'title' => $title ],
208 [ 'chatId' => $chatId, 'userId' => $userId ]
209 );
210 if ( $updated === false ) {
211 return new WP_REST_Response( [ 'success' => false, 'message' => 'Failed to update the discussion.' ], 500 );
212 }
213
214 return new WP_REST_Response( [ 'success' => true ], 200 );
215 }
216 catch( Exception $e ) {
217 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
218 }
219 }
220
221 public function cron_discussions() {
222 $this->check_db();
223
224 // NEW CHECK: Only run if auto-titling is enabled
225 if ( ! $this->core->get_option( 'chatbot_discussions_titling' ) ) {
226 return;
227 }
228 // END NEW CHECK
229
230 $now = date( 'Y-m-d H:i:s' );
231 $ten_days_ago = date( 'Y-m-d H:i:s', strtotime( '-10 days' ) );
232
233 // Get 5 latest discussions, not older than 10 days, which have no 'title' yet
234 $query = $this->wpdb->prepare(
235 "SELECT * FROM {$this->table_chats}
236 WHERE title IS NULL AND updated >= %s
237 ORDER BY updated DESC LIMIT 5",
238 $ten_days_ago
239 );
240 $discussions = $this->wpdb->get_results( $query );
241 if ( empty( $discussions ) ) {
242 return;
243 }
244
245 foreach ( $discussions as $discussion ) {
246 $this->generate_title_for_discussion( $discussion );
247 }
248 }
249
250 /**
251 * UI route for listing discussions.
252 * Here we add the "forced cron" logic for up to 5 discussions,
253 * but only if auto-titling is enabled.
254 */
255 public function rest_discussions_ui_list( $request ) {
256 try {
257 $params = $request->get_json_params();
258 $offset = isset( $params['offset'] ) ? $params['offset'] : 0;
259 $limit = isset( $params['limit'] ) ? $params['limit'] : 10;
260 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
261 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
262
263 if ( ! is_null( $customId ) ) {
264 $botId = $customId;
265 }
266 if ( is_null( $botId ) ) {
267 return new WP_REST_Response( [ 'success' => false, 'message' => 'Bot ID is required.' ], 200 );
268 }
269
270 $userId = get_current_user_id();
271 if ( ! $userId ) {
272 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be connected.' ], 200 );
273 }
274
275 $filters = [
276 [ 'accessor' => 'user', 'value' => $userId ],
277 [ 'accessor' => 'botId', 'value' => $botId ],
278 ];
279
280 // Retrieve the chats
281 $chats = $this->chats_query( [], $offset, $limit, $filters );
282
283 // NEW CHECK: only do forced titling if it's enabled
284 if ( $this->core->get_option( 'chatbot_discussions_titling' ) ) {
285 // "Forced cron" logic: check up to 5 that have no title
286 $counter = 0;
287 foreach ( $chats['rows'] as &$chatRow ) {
288 if ( $counter >= 5 ) {
289 break;
290 }
291 if ( empty( $chatRow['title'] ) && strtotime( $chatRow['updated'] ) >= strtotime( '-10 days' ) ) {
292 $discussionObj = (object) $chatRow;
293 $this->generate_title_for_discussion( $discussionObj );
294 $counter++;
295 }
296 }
297 // If you want the newly-updated titles to show up *immediately*:
298 $chats = $this->chats_query( [], $offset, $limit, $filters );
299 }
300 // END NEW CHECK
301
302
303
304 return new WP_REST_Response( [ 'success' => true, 'total' => $chats['total'], 'chats' => $chats['rows'] ], 200 );
305 }
306 catch( Exception $e ) {
307 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
308 }
309 }
310
311 public function rest_discussions_delete_admin( $request ) {
312 try {
313 $params = $request->get_json_params();
314 $chatsIds = $params['chatIds'];
315 if ( is_array( $chatsIds ) ) {
316 if ( count( $chatsIds ) === 0 ) {
317 $this->wpdb->query( "TRUNCATE TABLE $this->table_chats" );
318 }
319 foreach ( $chatsIds as $chatId ) {
320 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId ] );
321 }
322 }
323 return new WP_REST_Response( [ 'success' => true ], 200 );
324 }
325 catch( Exception $e ) {
326 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
327 }
328 }
329
330 public function rest_discussions_delete( $request ) {
331 try {
332 $params = $request->get_json_params();
333 $chatIds = isset( $params['chatIds'] ) ? $params['chatIds'] : null;
334
335 if ( ! is_array( $chatIds ) || empty( $chatIds ) ) {
336 return new WP_REST_Response( [ 'success' => false, 'message' => 'chatIds is required.' ], 400 );
337 }
338
339 $userId = get_current_user_id();
340 if ( ! $userId ) {
341 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be logged in.' ], 401 );
342 }
343
344 foreach ( $chatIds as $chatId ) {
345 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId, 'userId' => $userId ] );
346 }
347
348 return new WP_REST_Response( [ 'success' => true ], 200 );
349 }
350 catch( Exception $e ) {
351 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
352 }
353 }
354
355 // Get latest discussion for the given parameter
356 function get_discussion( $botId, $chatId ) {
357 $this->check_db();
358 $chat = $this->wpdb->get_row(
359 $this->wpdb->prepare(
360 "SELECT * FROM $this->table_chats WHERE chatId = %s AND botId = %s",
361 $chatId,
362 $botId
363 ),
364 ARRAY_A
365 );
366 if ( $chat ) {
367 $chat['messages'] = json_decode( $chat['messages'] );
368 return $chat;
369 }
370 return null;
371 }
372
373 function chats_query( $chats = [], $offset = 0, $limit = null, $filters = null, $sort = null ) {
374 $this->check_db();
375 $offset = ! empty( $offset ) ? intval( $offset ) : 0;
376 $limit = ! empty( $limit ) ? intval( $limit ) : 5;
377 $filters = ! empty( $filters ) ? $filters : [];
378 $this->core->sanitize_sort( $sort, 'updated', 'DESC' );
379
380 $where_clauses = [];
381 $where_values = [];
382
383 if ( is_array( $filters ) ) {
384 foreach ( $filters as $filter ) {
385 $value = $filter['value'];
386 if ( is_null( $value ) || $value === '' ) {
387 continue;
388 }
389 switch ( $filter['accessor'] ) {
390 case 'user':
391 $isIP = filter_var( $value, FILTER_VALIDATE_IP );
392 if ( $isIP ) {
393 $where_clauses[] = 'ip = %s';
394 $where_values[] = $value;
395 }
396 else {
397 $where_clauses[] = 'userId = %d';
398 $where_values[] = intval( $value );
399 }
400 break;
401 case 'botId':
402 $where_clauses[] = 'botId = %s';
403 $where_values[] = $value;
404 break;
405 case 'preview':
406 $like = '%' . $this->wpdb->esc_like( $value ) . '%';
407 $where_clauses[] = 'messages LIKE %s';
408 $where_values[] = $like;
409 break;
410 // Add other cases as needed
411 }
412 }
413 }
414
415 $where_sql = '';
416 if ( ! empty( $where_clauses ) ) {
417 $where_sql = 'WHERE ' . implode( ' AND ', $where_clauses );
418 }
419 $order_by = 'ORDER BY ' . esc_sql( $sort['accessor'] ) . ' ' . esc_sql( $sort['by'] );
420
421 $limit_sql = '';
422 if ( $limit > 0 ) {
423 $limit_sql = $this->wpdb->prepare( 'LIMIT %d, %d', $offset, $limit );
424 }
425
426 $query = "SELECT * FROM {$this->table_chats} {$where_sql} {$order_by} {$limit_sql}";
427 $chats['rows'] = $this->wpdb->get_results( $this->wpdb->prepare( $query, $where_values ), ARRAY_A );
428
429 // Get the total count
430 $count_query = "SELECT COUNT(*) FROM {$this->table_chats} {$where_sql}";
431 $chats['total'] = $this->wpdb->get_var( $this->wpdb->prepare( $count_query, $where_values ) );
432
433 return $chats;
434 }
435
436 public function chatbot_reply( $rawText, $query, $params, $extra ) {
437 global $mwai_core;
438 $userIp = $mwai_core->get_ip_address();
439 $userId = $mwai_core->get_user_id();
440 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
441 $chatId = $this->core->fix_chat_id( $query, $params );
442 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
443 $threadId = $query instanceof Meow_MWAI_Query_Assistant ? $query->threadId : null;
444 $storeId = $query instanceof Meow_MWAI_Query_Assistant ? $query->storeId : null;
445 $now = date( 'Y-m-d H:i:s' );
446
447 if ( ! empty( $customId ) ) {
448 $botId = $customId;
449 }
450 $newMessage = isset( $params['newMessage'] ) ? $params['newMessage'] : $query->get_message();
451
452 // If there is a file for "Vision", add it to the message
453 if ( isset( $query->filePurpose ) && $query->filePurpose === 'vision' && isset( $query->file ) ) {
454 $newMessage = "![Uploaded Image]({$query->file})\n" . $newMessage;
455 }
456
457 $this->check_db();
458 $chat = $this->wpdb->get_row(
459 $this->wpdb->prepare(
460 "SELECT * FROM $this->table_chats WHERE chatId = %s",
461 $chatId
462 )
463 );
464 $messageExtra = [
465 'embeddings' => isset( $extra['embeddings'] ) ? $extra['embeddings'] : null
466 ];
467 $chatExtra = [
468 'session' => $query->session,
469 'model' => $query->model,
470 ];
471 if ( ! empty( $query->temperature ) ) {
472 $chatExtra['temperature'] = $query->temperature;
473 }
474 if ( ! empty( $query->context ) ) {
475 $chatExtra['context'] = $query->context;
476 }
477 if ( ! empty( $params['parentBotId'] ) ) {
478 $chatExtra['parentBotId'] = $params['parentBotId'];
479 }
480 if ( $query instanceof Meow_MWAI_Query_Assistant ) {
481 $chatExtra['assistantId'] = $query->assistantId;
482 $chatExtra['threadId'] = $query->threadId;
483 $chatExtra['storeId'] = $query->storeId;
484 }
485
486 if ( $chat ) {
487 $chat->messages = json_decode( $chat->messages );
488 $chat->messages[] = [ 'role' => 'user', 'content' => $newMessage ];
489 $chat->messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
490 $chat->messages = json_encode( $chat->messages );
491 $this->wpdb->update(
492 $this->table_chats,
493 [
494 'userId' => $userId,
495 'messages' => $chat->messages,
496 'updated' => $now
497 ],
498 [ 'id' => $chat->id ]
499 );
500 }
501 else {
502 $startSentence = isset( $params['startSentence'] ) ? $params['startSentence'] : null;
503 $messages = [];
504 if ( ! empty( $startSentence ) ) {
505 $messages[] = [ 'role' => 'assistant', 'content' => $startSentence ];
506 }
507 $messages[] = [ 'role' => 'user', 'content' => $newMessage ];
508 $messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
509 $chat = [
510 'userId' => $userId,
511 'ip' => $userIp,
512 'messages' => json_encode( $messages ),
513 'extra' => json_encode( $chatExtra ),
514 'botId' => $botId,
515 'chatId' => $chatId,
516 'threadId' => $threadId,
517 'storeId' => $storeId,
518 'created' => $now,
519 'updated' => $now
520 ];
521 $this->wpdb->insert( $this->table_chats, $chat );
522 }
523 return $rawText;
524 }
525
526 function format_messages( $json, $format = 'html' ) {
527 $html = '';
528 if ( $format === 'html' ) {
529 try {
530 $conversation = json_decode( $json, true );
531 if ( json_last_error() !== JSON_ERROR_NONE ) {
532 return 'Invalid JSON format';
533 }
534 foreach ( $conversation as $message ) {
535 $role = ucfirst( $message['role'] );
536 $html .= '<p><strong>' . htmlspecialchars( $role ) . ':</strong> ' . htmlspecialchars( $message['content'] ) . '</p>';
537 }
538 }
539 catch( Exception $e ) {
540 error_log( $e->getMessage() );
541 return 'Error while formatting the message';
542 }
543 }
544 $html = apply_filters( 'mwai_discussion_format_messages', $html, $json, $format );
545 return $html;
546 }
547
548 /**
549 * Commits a discussion into the database (create or update if the same chatId is found).
550 *
551 * @param Meow_MWAI_Discussion $discussionObject
552 * @return bool True if success, false if error
553 */
554 public function commit_discussion( Meow_MWAI_Discussion $discussionObject ): bool {
555 $this->check_db();
556
557 // 1. Check if a discussion with the same chatId already exists
558 $chat = $this->wpdb->get_row(
559 $this->wpdb->prepare(
560 "SELECT * FROM {$this->table_chats} WHERE chatId = %s",
561 $discussionObject->chatId
562 ),
563 ARRAY_A
564 );
565
566 // 2. Prepare data for DB
567 $userIp = $this->core->get_ip_address();
568 $userId = $this->core->get_user_id();
569 $now = date( 'Y-m-d H:i:s' );
570
571 $data = [
572 'userId' => $userId,
573 'ip' => $userIp,
574 'botId' => $discussionObject->botId,
575 'chatId' => $discussionObject->chatId,
576 'messages'=> !empty( $discussionObject->messages ) ? wp_json_encode( $discussionObject->messages ) : '[]',
577 'extra' => !empty( $discussionObject->extra ) ? wp_json_encode( $discussionObject->extra ) : '{}',
578 'updated' => $now,
579 ];
580
581 // 3. Update if found, otherwise insert a new row
582 if ( $chat ) {
583 $updateRes = $this->wpdb->update(
584 $this->table_chats,
585 $data,
586 [ 'id' => $chat['id'] ]
587 );
588 if ( $updateRes === false ) {
589 error_log( 'Error updating discussion: ' . $this->wpdb->last_error );
590 return false;
591 }
592 }
593 else {
594 // For insertion, also set "created"
595 $data['created'] = $now;
596 $insertRes = $this->wpdb->insert( $this->table_chats, $data );
597 if ( $insertRes === false ) {
598 error_log( 'Error inserting discussion: ' . $this->wpdb->last_error );
599 return false;
600 }
601 }
602
603 return true;
604 }
605
606 function check_db() {
607 if ( $this->db_check ) {
608 return true;
609 }
610 $this->db_check = ! (
611 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
612 != strtolower( $this->table_chats )
613 );
614 if ( ! $this->db_check ) {
615 $this->create_db();
616 $this->db_check = ! (
617 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
618 != strtolower( $this->table_chats )
619 );
620 }
621
622 // LATER: REMOVE THIS AFTER MARCH 2025
623 // $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_chats LIKE 'title'" );
624 // if ( ! $this->db_check ) {
625 // $this->wpdb->query( "ALTER TABLE $this->table_chats ADD COLUMN title VARCHAR(64) NULL" );
626 // $this->db_check = true;
627 // }
628
629 return $this->db_check;
630 }
631
632 function create_db() {
633 $charset_collate = $this->wpdb->get_charset_collate();
634 $sqlLogs = "CREATE TABLE $this->table_chats (
635 id BIGINT(20) NOT NULL AUTO_INCREMENT,
636 userId BIGINT(20) NULL,
637 ip VARCHAR(64) NULL,
638 title VARCHAR(64) NULL,
639 messages TEXT NOT NULL NULL,
640 extra LONGTEXT NOT NULL NULL,
641 botId VARCHAR(64) NULL,
642 chatId VARCHAR(64) NOT NULL,
643 threadId VARCHAR(64) NULL,
644 storeId VARCHAR(64) NULL,
645 created DATETIME NOT NULL,
646 updated DATETIME NOT NULL,
647 PRIMARY KEY (id),
648 INDEX chatId (chatId)
649 ) $charset_collate;";
650 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
651 dbDelta( $sqlLogs );
652 }
653 }
654