PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.0
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.0
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 2 years ago wand.php 1 year ago
discussions.php
647 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
141 global $mwai;
142 $answer = $mwai->simpleTextQuery( $prompt, [ 'scope' => 'discussions' ] );
143
144 // Clean up the answer
145 $title = trim( $answer );
146 $title = rtrim( $title, ".!?:;,—–-–" ); // Remove trailing punctuation
147 $title = substr( $title, 0, 64 ); // Ensure less than 64 characters
148 if ( empty( $title ) ) {
149 $title = 'Untitled';
150 }
151
152 // Update the discussion with the title
153 $updated = $this->wpdb->update(
154 $this->table_chats,
155 [ 'title' => $title ],
156 [ 'id' => $discussion->id ]
157 );
158 if ( $updated === false ) {
159 error_log( "Failed to update the title for discussion ID {$discussion->id}" );
160 }
161 }
162
163 /**
164 * Admin route for listing discussions. No forced logic here.
165 */
166 public function rest_discussions_list( $request ) {
167 try {
168 $params = $request->get_json_params();
169 $offset = $params['offset'];
170 $limit = $params['limit'];
171 $filters = $params['filters'];
172 $sort = $params['sort'];
173
174 // Retrieve the chats
175 $chats = $this->chats_query( [], $offset, $limit, $filters, $sort );
176
177 return new WP_REST_Response( [ 'success' => true, 'total' => $chats['total'], 'chats' => $chats['rows'] ], 200 );
178 }
179 catch( Exception $e ) {
180 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
181 }
182 }
183
184 public function rest_discussions_ui_edit( $request ) {
185 try {
186 $params = $request->get_json_params();
187 $chatId = isset( $params['chatId'] ) ? sanitize_text_field( $params['chatId'] ) : null;
188 $title = isset( $params['title'] ) ? sanitize_text_field( $params['title'] ) : null;
189
190 if ( is_null( $chatId ) || is_null( $title ) ) {
191 return new WP_REST_Response( [ 'success' => false, 'message' => 'chatId and title are required.' ], 400 );
192 }
193
194 $userId = get_current_user_id();
195 if ( ! $userId ) {
196 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be logged in.' ], 401 );
197 }
198
199 // Update the discussion title for the current user
200 $updated = $this->wpdb->update(
201 $this->table_chats,
202 [ 'title' => $title ],
203 [ 'chatId' => $chatId, 'userId' => $userId ]
204 );
205 if ( $updated === false ) {
206 return new WP_REST_Response( [ 'success' => false, 'message' => 'Failed to update the discussion.' ], 500 );
207 }
208
209 return new WP_REST_Response( [ 'success' => true ], 200 );
210 }
211 catch( Exception $e ) {
212 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
213 }
214 }
215
216 public function cron_discussions() {
217 $this->check_db();
218
219 // NEW CHECK: Only run if auto-titling is enabled
220 if ( ! $this->core->get_option( 'chatbot_discussions_titling' ) ) {
221 return;
222 }
223 // END NEW CHECK
224
225 $now = date( 'Y-m-d H:i:s' );
226 $ten_days_ago = date( 'Y-m-d H:i:s', strtotime( '-10 days' ) );
227
228 // Get 5 latest discussions, not older than 10 days, which have no 'title' yet
229 $query = $this->wpdb->prepare(
230 "SELECT * FROM {$this->table_chats}
231 WHERE title IS NULL AND updated >= %s
232 ORDER BY updated DESC LIMIT 5",
233 $ten_days_ago
234 );
235 $discussions = $this->wpdb->get_results( $query );
236 if ( empty( $discussions ) ) {
237 return;
238 }
239
240 foreach ( $discussions as $discussion ) {
241 $this->generate_title_for_discussion( $discussion );
242 }
243 }
244
245 /**
246 * UI route for listing discussions.
247 * Here we add the "forced cron" logic for up to 5 discussions,
248 * but only if auto-titling is enabled.
249 */
250 public function rest_discussions_ui_list( $request ) {
251 try {
252 $params = $request->get_json_params();
253 $offset = isset( $params['offset'] ) ? $params['offset'] : 0;
254 $limit = isset( $params['limit'] ) ? $params['limit'] : 10;
255 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
256 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
257
258 if ( ! is_null( $customId ) ) {
259 $botId = $customId;
260 }
261 if ( is_null( $botId ) ) {
262 return new WP_REST_Response( [ 'success' => false, 'message' => 'Bot ID is required.' ], 200 );
263 }
264
265 $userId = get_current_user_id();
266 if ( ! $userId ) {
267 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be connected.' ], 200 );
268 }
269
270 $filters = [
271 [ 'accessor' => 'user', 'value' => $userId ],
272 [ 'accessor' => 'botId', 'value' => $botId ],
273 ];
274
275 // Retrieve the chats
276 $chats = $this->chats_query( [], $offset, $limit, $filters );
277
278 // NEW CHECK: only do forced titling if it's enabled
279 if ( $this->core->get_option( 'chatbot_discussions_titling' ) ) {
280 // "Forced cron" logic: check up to 5 that have no title
281 $counter = 0;
282 foreach ( $chats['rows'] as &$chatRow ) {
283 if ( $counter >= 5 ) {
284 break;
285 }
286 if ( empty( $chatRow['title'] ) && strtotime( $chatRow['updated'] ) >= strtotime( '-10 days' ) ) {
287 $discussionObj = (object) $chatRow;
288 $this->generate_title_for_discussion( $discussionObj );
289 $counter++;
290 }
291 }
292 // If you want the newly-updated titles to show up *immediately*:
293 $chats = $this->chats_query( [], $offset, $limit, $filters );
294 }
295 // END NEW CHECK
296
297 return new WP_REST_Response( [ 'success' => true, 'total' => $chats['total'], 'chats' => $chats['rows'] ], 200 );
298 }
299 catch( Exception $e ) {
300 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
301 }
302 }
303
304 public function rest_discussions_delete_admin( $request ) {
305 try {
306 $params = $request->get_json_params();
307 $chatsIds = $params['chatIds'];
308 if ( is_array( $chatsIds ) ) {
309 if ( count( $chatsIds ) === 0 ) {
310 $this->wpdb->query( "TRUNCATE TABLE $this->table_chats" );
311 }
312 foreach ( $chatsIds as $chatId ) {
313 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId ] );
314 }
315 }
316 return new WP_REST_Response( [ 'success' => true ], 200 );
317 }
318 catch( Exception $e ) {
319 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
320 }
321 }
322
323 public function rest_discussions_delete( $request ) {
324 try {
325 $params = $request->get_json_params();
326 $chatIds = isset( $params['chatIds'] ) ? $params['chatIds'] : null;
327
328 if ( ! is_array( $chatIds ) || empty( $chatIds ) ) {
329 return new WP_REST_Response( [ 'success' => false, 'message' => 'chatIds is required.' ], 400 );
330 }
331
332 $userId = get_current_user_id();
333 if ( ! $userId ) {
334 return new WP_REST_Response( [ 'success' => false, 'message' => 'You need to be logged in.' ], 401 );
335 }
336
337 foreach ( $chatIds as $chatId ) {
338 $this->wpdb->delete( $this->table_chats, [ 'chatId' => $chatId, 'userId' => $userId ] );
339 }
340
341 return new WP_REST_Response( [ 'success' => true ], 200 );
342 }
343 catch( Exception $e ) {
344 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
345 }
346 }
347
348 // Get latest discussion for the given parameter
349 function get_discussion( $botId, $chatId ) {
350 $this->check_db();
351 $chat = $this->wpdb->get_row(
352 $this->wpdb->prepare(
353 "SELECT * FROM $this->table_chats WHERE chatId = %s AND botId = %s",
354 $chatId,
355 $botId
356 ),
357 ARRAY_A
358 );
359 if ( $chat ) {
360 $chat['messages'] = json_decode( $chat['messages'] );
361 return $chat;
362 }
363 return null;
364 }
365
366 function chats_query( $chats = [], $offset = 0, $limit = null, $filters = null, $sort = null ) {
367 $this->check_db();
368 $offset = ! empty( $offset ) ? intval( $offset ) : 0;
369 $limit = ! empty( $limit ) ? intval( $limit ) : 5;
370 $filters = ! empty( $filters ) ? $filters : [];
371 $this->core->sanitize_sort( $sort, 'updated', 'DESC' );
372
373 $where_clauses = [];
374 $where_values = [];
375
376 if ( is_array( $filters ) ) {
377 foreach ( $filters as $filter ) {
378 $value = $filter['value'];
379 if ( is_null( $value ) || $value === '' ) {
380 continue;
381 }
382 switch ( $filter['accessor'] ) {
383 case 'user':
384 $isIP = filter_var( $value, FILTER_VALIDATE_IP );
385 if ( $isIP ) {
386 $where_clauses[] = 'ip = %s';
387 $where_values[] = $value;
388 }
389 else {
390 $where_clauses[] = 'userId = %d';
391 $where_values[] = intval( $value );
392 }
393 break;
394 case 'botId':
395 $where_clauses[] = 'botId = %s';
396 $where_values[] = $value;
397 break;
398 case 'preview':
399 $like = '%' . $this->wpdb->esc_like( $value ) . '%';
400 $where_clauses[] = 'messages LIKE %s';
401 $where_values[] = $like;
402 break;
403 // Add other cases as needed
404 }
405 }
406 }
407
408 $where_sql = '';
409 if ( ! empty( $where_clauses ) ) {
410 $where_sql = 'WHERE ' . implode( ' AND ', $where_clauses );
411 }
412 $order_by = 'ORDER BY ' . esc_sql( $sort['accessor'] ) . ' ' . esc_sql( $sort['by'] );
413
414 $limit_sql = '';
415 if ( $limit > 0 ) {
416 $limit_sql = $this->wpdb->prepare( 'LIMIT %d, %d', $offset, $limit );
417 }
418
419 $query = "SELECT * FROM {$this->table_chats} {$where_sql} {$order_by} {$limit_sql}";
420 $chats['rows'] = $this->wpdb->get_results( $this->wpdb->prepare( $query, $where_values ), ARRAY_A );
421
422 // Get the total count
423 $count_query = "SELECT COUNT(*) FROM {$this->table_chats} {$where_sql}";
424 $chats['total'] = $this->wpdb->get_var( $this->wpdb->prepare( $count_query, $where_values ) );
425
426 return $chats;
427 }
428
429 public function chatbot_reply( $rawText, $query, $params, $extra ) {
430 global $mwai_core;
431 $userIp = $mwai_core->get_ip_address();
432 $userId = $mwai_core->get_user_id();
433 $botId = isset( $params['botId'] ) ? $params['botId'] : null;
434 $chatId = $this->core->fix_chat_id( $query, $params );
435 $customId = isset( $params['customId'] ) ? $params['customId'] : null;
436 $threadId = $query instanceof Meow_MWAI_Query_Assistant ? $query->threadId : null;
437 $storeId = $query instanceof Meow_MWAI_Query_Assistant ? $query->storeId : null;
438 $now = date( 'Y-m-d H:i:s' );
439
440 if ( ! empty( $customId ) ) {
441 $botId = $customId;
442 }
443 $newMessage = isset( $params['newMessage'] ) ? $params['newMessage'] : $query->get_message();
444
445 // If there is a file for "Vision", add it to the message
446 if ( isset( $query->filePurpose ) && $query->filePurpose === 'vision' && isset( $query->file ) ) {
447 $newMessage = "![Uploaded Image]({$query->file})\n" . $newMessage;
448 }
449
450 $this->check_db();
451 $chat = $this->wpdb->get_row(
452 $this->wpdb->prepare(
453 "SELECT * FROM $this->table_chats WHERE chatId = %s",
454 $chatId
455 )
456 );
457 $messageExtra = [
458 'embeddings' => isset( $extra['embeddings'] ) ? $extra['embeddings'] : null
459 ];
460 $chatExtra = [
461 'session' => $query->session,
462 'model' => $query->model,
463 ];
464 if ( ! empty( $query->temperature ) ) {
465 $chatExtra['temperature'] = $query->temperature;
466 }
467 if ( ! empty( $query->context ) ) {
468 $chatExtra['context'] = $query->context;
469 }
470 if ( ! empty( $params['parentBotId'] ) ) {
471 $chatExtra['parentBotId'] = $params['parentBotId'];
472 }
473 if ( $query instanceof Meow_MWAI_Query_Assistant ) {
474 $chatExtra['assistantId'] = $query->assistantId;
475 $chatExtra['threadId'] = $query->threadId;
476 $chatExtra['storeId'] = $query->storeId;
477 }
478
479 if ( $chat ) {
480 $chat->messages = json_decode( $chat->messages );
481 $chat->messages[] = [ 'role' => 'user', 'content' => $newMessage ];
482 $chat->messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
483 $chat->messages = json_encode( $chat->messages );
484 $this->wpdb->update(
485 $this->table_chats,
486 [
487 'userId' => $userId,
488 'messages' => $chat->messages,
489 'updated' => $now
490 ],
491 [ 'id' => $chat->id ]
492 );
493 }
494 else {
495 $startSentence = isset( $params['startSentence'] ) ? $params['startSentence'] : null;
496 $messages = [];
497 if ( ! empty( $startSentence ) ) {
498 $messages[] = [ 'role' => 'assistant', 'content' => $startSentence ];
499 }
500 $messages[] = [ 'role' => 'user', 'content' => $newMessage ];
501 $messages[] = [ 'role' => 'assistant', 'content' => $rawText, 'extra' => $messageExtra ];
502 $chat = [
503 'userId' => $userId,
504 'ip' => $userIp,
505 'messages' => json_encode( $messages ),
506 'extra' => json_encode( $chatExtra ),
507 'botId' => $botId,
508 'chatId' => $chatId,
509 'threadId' => $threadId,
510 'storeId' => $storeId,
511 'created' => $now,
512 'updated' => $now
513 ];
514 $this->wpdb->insert( $this->table_chats, $chat );
515 }
516 return $rawText;
517 }
518
519 function format_messages( $json, $format = 'html' ) {
520 $html = '';
521 if ( $format === 'html' ) {
522 try {
523 $conversation = json_decode( $json, true );
524 if ( json_last_error() !== JSON_ERROR_NONE ) {
525 return 'Invalid JSON format';
526 }
527 foreach ( $conversation as $message ) {
528 $role = ucfirst( $message['role'] );
529 $html .= '<p><strong>' . htmlspecialchars( $role ) . ':</strong> ' . htmlspecialchars( $message['content'] ) . '</p>';
530 }
531 }
532 catch( Exception $e ) {
533 error_log( $e->getMessage() );
534 return 'Error while formatting the message';
535 }
536 }
537 $html = apply_filters( 'mwai_discussion_format_messages', $html, $json, $format );
538 return $html;
539 }
540
541 /**
542 * Commits a discussion into the database (create or update if the same chatId is found).
543 *
544 * @param Meow_MWAI_Discussion $discussionObject
545 * @return bool True if success, false if error
546 */
547 public function commit_discussion( Meow_MWAI_Discussion $discussionObject ): bool {
548 $this->check_db();
549
550 // 1. Check if a discussion with the same chatId already exists
551 $chat = $this->wpdb->get_row(
552 $this->wpdb->prepare(
553 "SELECT * FROM {$this->table_chats} WHERE chatId = %s",
554 $discussionObject->chatId
555 ),
556 ARRAY_A
557 );
558
559 // 2. Prepare data for DB
560 $userIp = $this->core->get_ip_address();
561 $userId = $this->core->get_user_id();
562 $now = date( 'Y-m-d H:i:s' );
563
564 $data = [
565 'userId' => $userId,
566 'ip' => $userIp,
567 'botId' => $discussionObject->botId,
568 'chatId' => $discussionObject->chatId,
569 'messages'=> !empty( $discussionObject->messages ) ? wp_json_encode( $discussionObject->messages ) : '[]',
570 'extra' => !empty( $discussionObject->extra ) ? wp_json_encode( $discussionObject->extra ) : '{}',
571 'updated' => $now,
572 ];
573
574 // 3. Update if found, otherwise insert a new row
575 if ( $chat ) {
576 $updateRes = $this->wpdb->update(
577 $this->table_chats,
578 $data,
579 [ 'id' => $chat['id'] ]
580 );
581 if ( $updateRes === false ) {
582 error_log( 'Error updating discussion: ' . $this->wpdb->last_error );
583 return false;
584 }
585 }
586 else {
587 // For insertion, also set "created"
588 $data['created'] = $now;
589 $insertRes = $this->wpdb->insert( $this->table_chats, $data );
590 if ( $insertRes === false ) {
591 error_log( 'Error inserting discussion: ' . $this->wpdb->last_error );
592 return false;
593 }
594 }
595
596 return true;
597 }
598
599 function check_db() {
600 if ( $this->db_check ) {
601 return true;
602 }
603 $this->db_check = ! (
604 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
605 != strtolower( $this->table_chats )
606 );
607 if ( ! $this->db_check ) {
608 $this->create_db();
609 $this->db_check = ! (
610 strtolower( $this->wpdb->get_var( "SHOW TABLES LIKE '$this->table_chats'" ) )
611 != strtolower( $this->table_chats )
612 );
613 }
614
615 // LATER: REMOVE THIS AFTER MARCH 2025
616 $this->db_check = $this->db_check && $this->wpdb->get_var( "SHOW COLUMNS FROM $this->table_chats LIKE 'title'" );
617 if ( ! $this->db_check ) {
618 $this->wpdb->query( "ALTER TABLE $this->table_chats ADD COLUMN title VARCHAR(64) NULL" );
619 $this->db_check = true;
620 }
621
622 return $this->db_check;
623 }
624
625 function create_db() {
626 $charset_collate = $this->wpdb->get_charset_collate();
627 $sqlLogs = "CREATE TABLE $this->table_chats (
628 id BIGINT(20) NOT NULL AUTO_INCREMENT,
629 userId BIGINT(20) NULL,
630 ip VARCHAR(64) NULL,
631 title VARCHAR(64) NULL,
632 messages TEXT NOT NULL NULL,
633 extra LONGTEXT NOT NULL NULL,
634 botId VARCHAR(64) NULL,
635 chatId VARCHAR(64) NOT NULL,
636 threadId VARCHAR(64) NULL,
637 storeId VARCHAR(64) NULL,
638 created DATETIME NOT NULL,
639 updated DATETIME NOT NULL,
640 PRIMARY KEY (id),
641 INDEX chatId (chatId)
642 ) $charset_collate;";
643 require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
644 dbDelta( $sqlLogs );
645 }
646 }
647