PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / app / Agent / Controllers / ChatHistoryController.php

ChatHistoryController.php in Extendify 3.1.0, at app/Agent/Controllers/ChatHistoryController.php

218 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Store info about messages/events from chat
5 */
6
7 namespace Extendify\Agent\Controllers;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Shared\Services\Sanitizer;
12
13 /**
14 * The controller
15 */
16
17 class ChatHistoryController
18 {
19 /**
20 * Initialize the controller and set up the database table.
21 *
22 * @return \WP_REST_Response
23 */
24 public static function init()
25 {
26 self::setupChatHistoryTable();
27 }
28
29 /**
30 * Get the last 150 messages
31 *
32 * @return array
33 */
34 public static function getChatHistory($user_id = null)
35 {
36 global $wpdb;
37 $table = $wpdb->prefix . 'extendify_agent_events';
38 $user_id = $user_id ?: get_current_user_id();
39
40 $results = $wpdb->get_results(
41 $wpdb->prepare(
42 "SELECT * FROM $table WHERE user_id = %d ORDER BY id DESC LIMIT 150",
43 $user_id
44 ),
45 ARRAY_A
46 );
47 return array_map(function ($item) {
48 return [
49 'id' => $item['event_id'],
50 'type' => $item['type'],
51 'details' => json_decode($item['details'], true),
52 ];
53 }, $results);
54 }
55
56 /**
57 * Delete the user's chat history, if the table exists.
58 *
59 * @param int|null $user_id The user ID, defaults to the current user.
60 * @return void
61 */
62 public static function clear($user_id = null)
63 {
64 global $wpdb;
65 $table = $wpdb->prefix . 'extendify_agent_events';
66 $user_id = $user_id ?: get_current_user_id();
67
68 if (!$wpdb->get_var("SHOW TABLES LIKE '$table'")) {
69 return;
70 }
71
72 $wpdb->query(
73 $wpdb->prepare("DELETE FROM $table WHERE user_id = %d", $user_id)
74 );
75 }
76
77 /**
78 * Return the data
79 *
80 * @return \WP_REST_Response
81 */
82 public static function get()
83 {
84 $messages = self::getChatHistory();
85 $state = ['state' => ['messages' => Sanitizer::sanitizeArray($messages)]];
86 return new \WP_REST_Response($state);
87 }
88
89 /**
90 * Persist the data
91 *
92 * @param \WP_REST_Request $request - The request.
93 * @return \WP_REST_Response
94 */
95 public static function store($request)
96 {
97 global $wpdb;
98 $table = $wpdb->prefix . 'extendify_agent_events';
99 $user_id = get_current_user_id();
100
101 $state = $request->get_param('state');
102 $parsed = is_string($state) ? json_decode($state, true) : $state;
103 $messages = $parsed['state']['messages'] ?? [];
104
105 // Find the latest event and only add new messages since then.
106 /* TODO: Maybe we may need a check like:
107 "if this message is assistant and so is the last, delete the last one" to avoid duplicates on retries."
108 */
109 $latest = $wpdb->get_var(
110 $wpdb->prepare(
111 "SELECT event_id FROM $table WHERE user_id = %d ORDER BY created_at DESC LIMIT 1",
112 $user_id
113 )
114 );
115 $startIndex = 0;
116 if ($latest) {
117 foreach ($messages as $i => $msg) {
118 if ($msg['id'] === $latest) {
119 $startIndex = $i + 1;
120 break;
121 }
122 }
123 }
124
125 $toInsert = array_slice($messages, $startIndex);
126 foreach ($toInsert as $msg) {
127 self::upsertEvent($msg, $user_id);
128 }
129
130 return self::get();
131 }
132
133 /**
134 * Upsert an event into the database.
135 *
136 * @param array $msg The message data.
137 * @param int $user_id The user ID.
138 * @return void
139 */
140 private static function upsertEvent($msg, $user_id)
141 {
142 global $wpdb;
143 $table = $wpdb->prefix . 'extendify_agent_events';
144 $created_at = current_time('mysql');
145 $event_id = Sanitizer::sanitizeText($msg['id']);
146 $type = Sanitizer::sanitizeText($msg['type']);
147 $details = wp_json_encode(Sanitizer::sanitizeArray($msg['details']));
148
149 $sql = $wpdb->prepare(
150 "INSERT INTO $table (created_at, event_id, type, details, user_id)
151 VALUES (%s, %s, %s, %s, %d)
152 ON DUPLICATE KEY UPDATE
153 created_at = VALUES(created_at),
154 type = VALUES(type),
155 details = VALUES(details)",
156 $created_at,
157 $event_id,
158 $type,
159 $details,
160 $user_id
161 );
162 $wpdb->query($sql);
163 }
164
165 /**
166 * Ensures the custom table exists and is up to date for MySQL.
167 * Creates the table if missing, adds missing columns, and ensures an index on user_id.
168 *
169 * @return void
170 */
171 private static function setupChatHistoryTable()
172 {
173 global $wpdb;
174 $table = $wpdb->prefix . 'extendify_agent_events';
175 $columns = [
176 'id' => "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY",
177 'created_at' => "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP",
178 'event_id' => "VARCHAR(64) NOT NULL",
179 'type' => "VARCHAR(64) NOT NULL",
180 'details' => "LONGTEXT",
181 'user_id' => "BIGINT UNSIGNED NOT NULL"
182 ];
183
184 // Check if the table exists and create it if not
185 $exists = $wpdb->get_var("SHOW TABLES LIKE '$table'");
186 if (!$exists) {
187 $cols = [];
188 foreach ($columns as $name => $type) {
189 $cols[] = "$name $type";
190 }
191 $sql = "CREATE TABLE $table (" . implode(',', $cols) . ", INDEX(user_id))";
192 $sql .= " " . $wpdb->get_charset_collate() . ";";
193 $wpdb->query($sql);
194 return;
195 }
196
197 // Check existing columns and add missing ones
198 $existingCols = array_column($wpdb->get_results("SHOW COLUMNS FROM $table", ARRAY_A), 'Field');
199 foreach ($columns as $name => $type) {
200 if (!in_array($name, $existingCols, true)) {
201 $wpdb->query("ALTER TABLE $table ADD COLUMN $name $type");
202 }
203 }
204
205 // Unique index on (event_id, user_id)
206 $uniqueIndex = $wpdb->get_results("SHOW INDEX FROM $table WHERE Key_name = 'unique_event_id'", ARRAY_A);
207 if (empty($uniqueIndex)) {
208 $wpdb->query("CREATE UNIQUE INDEX unique_event_id ON $table(event_id, user_id)");
209 }
210
211 // Regular index on user_id
212 $userIndex = $wpdb->get_results("SHOW INDEX FROM $table WHERE Key_name = 'user_id'", ARRAY_A);
213 if (empty($userIndex)) {
214 $wpdb->query("CREATE INDEX user_id ON $table(user_id)");
215 }
216 }
217 }
218