PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.4
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 search.php 1 year ago security.php 1 year ago tasks.php 1 year ago wand.php 1 year ago
discussions.php
673 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 // Get paging setting from options
260 $paging_option = $this->core->get_option( 'chatbot_discussions_paging' );
261 if ( $paging_option === 'None' ) {
262 $default_limit = 999; // Show all discussions
263 } else {
264 $default_limit = is_numeric( $paging_option ) ? intval( $paging_option ) : 10; // Fallback to 10
265 }
266 $limit = isset( $params['limit'] ) ? $params['limit'] : $default_limit;
267 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
268 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
269
270 if ( ! is_null( $customId ) ) {
271 $botId = $customId;
272 }
273 if ( is_null( $botId ) ) {
274 return new WP_REST_Response( [ 'success' => false, 'message' => 'Bot ID is required.' ], 200 );
275 }
276
277 $userId = get_current_user_id();
278 if ( ! $userId ) {
279 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be connected.' ], 200 );
280 }
281
282 $filters = [
283 [ 'accessor' => 'user', 'value' => $userId ],
284 [ 'accessor' => 'botId', 'value' => $botId ],
285 ];
286
287 // Retrieve the chats
288 $chats = $this->chats_query( [], $offset, $limit, $filters );
289
290 // NEW CHECK: only do forced titling if it's enabled
291 if ( $this->core->get_option( 'chatbot_discussions_titling' ) ) {
292 // "Forced cron" logic: check up to 5 that have no title
293 $counter = 0;
294 foreach ( $chats['rows'] as &$chatRow ) {
295 if ( $counter >= 5 ) {
296 break;
297 }
298 if ( empty( $chatRow['title'] ) && strtotime( $chatRow['updated'] ) >= strtotime( '-10 days' ) ) {
299 $discussionObj = (object) $chatRow;
300 $this->generate_title_for_discussion( $discussionObj );
301 $counter++;
302 }
303 }
304 // If you want the newly-updated titles to show up *immediately*:
305 $chats = $this->chats_query( [], $offset, $limit, $filters );
306 }
307 // END NEW CHECK
308
309
310
311 return new WP_REST_Response( [ 'success' => true, 'total' => $chats['total'], 'chats' => $chats['rows'] ], 200 );
312 }
313 catch( Exception $e ) {
314 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
315 }
316 }
317
318 public function rest_discussions_delete_admin( $request ) {
319 try {
320 $params = $request->get_json_params();
321 $chatsIds = $params['chatIds'];
322 if ( is_array( $chatsIds ) ) {
323 if ( count( $chatsIds ) === 0 ) {
324 $this->wpdb->query( "TRUNCATE TABLE $this->table_chats" );
325 }
326 foreach ( $chatsIds as $chatId ) {
327 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId ] );
328 }
329 }
330 return new WP_REST_Response( [ 'success' => true ], 200 );
331 }
332 catch( Exception $e ) {
333 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
334 }
335 }
336
337 public function rest_discussions_delete( $request ) {
338 try {
339 $params = $request->get_json_params();
340 $chatIds = isset( $params['chatIds'] ) ? $params['chatIds'] : null;
341
342 if ( ! is_array( $chatIds ) || empty( $chatIds ) ) {
343 return new WP_REST_Response( [ 'success' => false, 'message' => 'chatIds is required.' ], 400 );
344 }
345
346 $userId = get_current_user_id();
347 if ( ! $userId ) {
348 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be logged in.' ], 401 );
349 }
350
351 foreach ( $chatIds as $chatId ) {
352 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId, 'userId' => $userId ] );
353 }
354
355 return new WP_REST_Response( [ 'success' => true ], 200 );
356 }
357 catch( Exception $e ) {
358 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
359 }
360 }
361
362 // Get latest discussion for the given parameter
363 function get_discussion( $botId, $chatId ) {
364 $this->check_db();
365 $chat = $this->wpdb->get_row(
366 $this->wpdb->prepare(
367 "SELECT * FROM $this->table_chats WHERE chatId = %s AND botId = %s",
368 $chatId,
369 $botId
370 ),
371 ARRAY_A
372 );
373 if ( $chat ) {
374 $chat['messages'] = json_decode( $chat['messages'] );
375 return $chat;
376 }
377 return null;
378 }
379
380 function chats_query( $chats = [], $offset = 0, $limit = null, $filters = null, $sort = null ) {
381 $this->check_db();
382 $offset = ! empty( $offset ) ? intval( $offset ) : 0;
383 $limit = ! empty( $limit ) ? intval( $limit ) : 5;
384 $filters = ! empty( $filters ) ? $filters : [];
385 $this->core->sanitize_sort( $sort, 'updated', 'DESC' );
386
387 $where_clauses = [];
388 $where_values = [];
389
390 if ( is_array( $filters ) ) {
391 foreach ( $filters as $filter ) {
392 $value = $filter['value'];
393 if ( is_null( $value ) || $value === '' ) {
394 continue;
395 }
396 switch ( $filter['accessor'] ) {
397 case 'user':
398 $isIP = filter_var( $value, FILTER_VALIDATE_IP );
399 if ( $isIP ) {
400 $where_clauses[] = 'ip = %s';
401 $where_values[] = $value;
402 }
403 else {
404 $where_clauses[] = 'userId = %d';
405 $where_values[] = intval( $value );
406 }
407 break;
408 case 'botId':
409 $where_clauses[] = 'botId = %s';
410 $where_values[] = $value;
411 break;
412 case 'preview':
413 $like = '%' . $this->wpdb->esc_like( $value ) . '%';
414 $where_clauses[] = 'messages LIKE %s';
415 $where_values[] = $like;
416 break;
417 // Add other cases as needed
418 }
419 }
420 }
421
422 $where_sql = '';
423 if ( ! empty( $where_clauses ) ) {
424 $where_sql = 'WHERE ' . implode( ' AND ', $where_clauses );
425 }
426 $order_by = 'ORDER BY ' . esc_sql( $sort['accessor'] ) . ' ' . esc_sql( $sort['by'] );
427
428 $limit_sql = '';
429 if ( $limit > 0 ) {
430 $limit_sql = $this->wpdb->prepare( 'LIMIT %d, %d', $offset, $limit );
431 }
432
433 $query = "SELECT * FROM {$this->table_chats} {$where_sql} {$order_by} {$limit_sql}";
434 $chats['rows'] = $this->wpdb->get_results( $this->wpdb->prepare( $query, $where_values ), ARRAY_A );
435
436 // Get the total count
437 $count_query = "SELECT COUNT(*) FROM {$this->table_chats} {$where_sql}";
438 $chats['total'] = $this->wpdb->get_var( $this->wpdb->prepare( $count_query, $where_values ) );
439
440 return $chats;
441 }
442
443 public function chatbot_reply( $rawText, $query, $params, $extra ) {
444 global $mwai_core;
445 $userIp = $mwai_core->get_ip_address();
446 $userId = $mwai_core->get_user_id();
447 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
448 $chatId = $this->core->fix_chat_id( $query, $params );
449 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
450 $threadId = $query instanceof Meow_MWAI_Query_Assistant ? $query->threadId : null;
451 $storeId = $query instanceof Meow_MWAI_Query_Assistant ? $query->storeId : null;
452 $now = date( 'Y-m-d H:i:s' );
453
454 if ( ! empty( $customId ) ) {
455 $botId = $customId;
456 }
457 $newMessage = isset( $params['newMessage'] ) ? $params['newMessage'] : $query->get_message();
458
459 // If there is a file for "Vision", add it to the message
460 if ( isset( $query->filePurpose ) && $query->filePurpose === 'vision' && isset( $query->file ) ) {
461 $newMessage = "![Uploaded Image]({$query->file})\n" . $newMessage;
462 }
463
464 $this->check_db();
465 $chat = $this->wpdb->get_row(
466 $this->wpdb->prepare(
467 "SELECT * FROM $this->table_chats WHERE chatId = %s",
468 $chatId
469 )
470 );
471 $messageExtra = [
472 'embeddings' => isset( $extra['embeddings'] ) ? $extra['embeddings'] : null
473 ];
474 $chatExtra = [
475 'session' => $query->session,
476 'model' => $query->model,
477 ];
478 if ( ! empty( $query->temperature ) ) {
479 $chatExtra['temperature'] = $query->temperature;
480 }
481 if ( ! empty( $query->context ) ) {
482 $chatExtra['context'] = $query->context;
483 }
484 if ( ! empty( $params['parentBotId'] ) ) {
485 $chatExtra['parentBotId'] = $params['parentBotId'];
486 }
487 if ( $query instanceof Meow_MWAI_Query_Assistant ) {
488 $chatExtra['assistantId'] = $query->assistantId;
489 $chatExtra['threadId'] = $query->threadId;
490 $chatExtra['storeId'] = $query->storeId;
491 }
492
493 // Store response ID and date for Responses API
494 if ( !empty( $extra['responseId'] ) ) {
495 $chatExtra['previousResponseId'] = $extra['responseId'];
496 $chatExtra['previousResponseDate'] = $now;
497 }
498
499 if ( $chat ) {
500 $chat->messages = json_decode( $chat->messages );
501 $chat->messages[] = [ 'role' => 'user', 'content' => $newMessage ];
502 $chat->messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
503 $chat->messages = json_encode( $chat->messages );
504
505 // Update or merge extra data
506 $existingExtra = json_decode( $chat->extra, true ) ?: [];
507 $mergedExtra = array_merge( $existingExtra, $chatExtra );
508
509 $this->wpdb->update(
510 $this->table_chats,
511 [
512 'userId' => $userId,
513 'messages' => $chat->messages,
514 'extra' => json_encode( $mergedExtra ),
515 'updated' => $now
516 ],
517 [ 'id' => $chat->id ]
518 );
519 }
520 else {
521 $startSentence = isset( $params['startSentence'] ) ? $params['startSentence'] : null;
522 $messages = [];
523 if ( ! empty( $startSentence ) ) {
524 $messages[] = [ 'role' => 'assistant', 'content' => $startSentence ];
525 }
526 $messages[] = [ 'role' => 'user', 'content' => $newMessage ];
527 $messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
528 $chat = [
529 'userId' => $userId,
530 'ip' => $userIp,
531 'messages' => json_encode( $messages ),
532 'extra' => json_encode( $chatExtra ),
533 'botId' => $botId,
534 'chatId' => $chatId,
535 'threadId' => $threadId,
536 'storeId' => $storeId,
537 'created' => $now,
538 'updated' => $now
539 ];
540 $this->wpdb->insert( $this->table_chats, $chat );
541 }
542 return $rawText;
543 }
544
545 function format_messages( $json, $format = 'html' ) {
546 $html = '';
547 if ( $format === 'html' ) {
548 try {
549 $conversation = json_decode( $json, true );
550 if ( json_last_error() !== JSON_ERROR_NONE ) {
551 return 'Invalid JSON format';
552 }
553 foreach ( $conversation as $message ) {
554 $role = ucfirst( $message['role'] );
555 $html .= '<p><strong>' . htmlspecialchars( $role ) . ':</strong> ' . htmlspecialchars( $message['content'] ) . '</p>';
556 }
557 }
558 catch( Exception $e ) {
559 error_log( $e->getMessage() );
560 return 'Error while formatting the message';
561 }
562 }
563 $html = apply_filters( 'mwai_discussion_format_messages', $html, $json, $format );
564 return $html;
565 }
566
567 /**
568 * Commits a discussion into the database (create or update if the same chatId is found).
569 *
570 * @param Meow_MWAI_Discussion $discussionObject
571 * @return bool True if success, false if error
572 */
573 public function commit_discussion( Meow_MWAI_Discussion $discussionObject ): bool {
574 $this->check_db();
575
576 // 1. Check if a discussion with the same chatId already exists
577 $chat = $this->wpdb->get_row(
578 $this->wpdb->prepare(
579 "SELECT * FROM {$this->table_chats} WHERE chatId = %s",
580 $discussionObject->chatId
581 ),
582 ARRAY_A
583 );
584
585 // 2. Prepare data for DB
586 $userIp = $this->core->get_ip_address();
587 $userId = $this->core->get_user_id();
588 $now = date( 'Y-m-d H:i:s' );
589
590 $data = [
591 'userId' => $userId,
592 'ip' => $userIp,
593 'botId' => $discussionObject->botId,
594 'chatId' => $discussionObject->chatId,
595 'messages'=> !empty( $discussionObject->messages ) ? wp_json_encode( $discussionObject->messages ) : '[]',
596 'extra' => !empty( $discussionObject->extra ) ? wp_json_encode( $discussionObject->extra ) : '{}',
597 'updated' => $now,
598 ];
599
600 // 3. Update if found, otherwise insert a new row
601 if ( $chat ) {
602 $updateRes = $this->wpdb->update(
603 $this->table_chats,
604 $data,
605 [ 'id' => $chat['id'] ]
606 );
607 if ( $updateRes === false ) {
608 error_log( 'Error updating discussion: ' . $this->wpdb->last_error );
609 return false;
610 }
611 }
612 else {
613 // For insertion, also set "created"
614 $data['created'] = $now;
615 $insertRes = $this->wpdb->insert( $this->table_chats, $data );
616 if ( $insertRes === false ) {
617 error_log( 'Error inserting discussion: ' . $this->wpdb->last_error );
618 return false;
619 }
620 }
621
622 return true;
623 }
624
625 function check_db() {
626 if ( $this->db_check ) {
627 return true;
628 }
629 $this->db_check = ! (
630 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
631 != strtolower( $this->table_chats )
632 );
633 if ( ! $this->db_check ) {
634 $this->create_db();
635 $this->db_check = ! (
636 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
637 != strtolower( $this->table_chats )
638 );
639 }
640
641 // LATER: REMOVE THIS AFTER MARCH 2025
642 // $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_chats LIKE 'title'" );
643 // if ( ! $this->db_check ) {
644 // $this->wpdb->query( "ALTER TABLE $this->table_chats ADD COLUMN title VARCHAR(64) NULL" );
645 // $this->db_check = true;
646 // }
647
648 return $this->db_check;
649 }
650
651 function create_db() {
652 $charset_collate = $this->wpdb->get_charset_collate();
653 $sqlLogs = "CREATE TABLE $this->table_chats (
654 id BIGINT(20) NOT NULL AUTO_INCREMENT,
655 userId BIGINT(20) NULL,
656 ip VARCHAR(64) NULL,
657 title VARCHAR(64) NULL,
658 messages TEXT NOT NULL NULL,
659 extra LONGTEXT NOT NULL NULL,
660 botId VARCHAR(64) NULL,
661 chatId VARCHAR(64) NOT NULL,
662 threadId VARCHAR(64) NULL,
663 storeId VARCHAR(64) NULL,
664 created DATETIME NOT NULL,
665 updated DATETIME NOT NULL,
666 PRIMARY KEY (id),
667 INDEX chatId (chatId)
668 ) $charset_collate;";
669 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
670 dbDelta( $sqlLogs );
671 }
672 }
673