PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.2.0
Fluent Support – Helpdesk & Customer Support Ticket System v2.2.0
2.4.0 2.3.2 2.3.1 2.3.0 2.2.1 2.2.0 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.4.0 1.4.1 1.4.2 1.4.5 1.4.6 1.4.7 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 All 68 releases
fluent-support / app / Services / Integrations / FluentBot / FluentBotAPI.php

FluentBotAPI.php in Fluent Support – Helpdesk & Customer Support Ticket System 2.2.0, at app/Services/Integrations/FluentBot/FluentBotAPI.php

192 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentSupport\App\Services\Integrations\FluentBot;
4
5 use WP_Error;
6
7 class FluentBotAPI
8 {
9 protected $apiUrl;
10
11 public function __construct(string $apiUrl)
12 {
13 $this->apiUrl = $apiUrl;
14 }
15
16 public function makeRequest(int $ticketId, $prompt, array $args = [])
17 {
18 $response = $this->sendRequest($args);
19
20 if (is_wp_error($response)) {
21 $message = $response->get_error_message();
22 $code = $response->get_error_code();
23 return new \WP_Error($code, $message);
24 }
25
26 $rawBody = wp_remote_retrieve_body($response);
27 $responseBody = json_decode($rawBody, true) ?? [];
28
29 if (!$responseBody || !is_array($responseBody)) {
30 return new \WP_Error('fluent_bot_error', __('Invalid or empty response from API', 'fluent-support'));
31 }
32
33 if (!empty($responseBody['error'])) {
34 $message = $responseBody['error']['message'] ?? __('Unknown error occurred', 'fluent-support');
35 return new \WP_Error('fluent_bot_error', $message);
36 }
37
38 $statusCode = wp_remote_retrieve_response_code($response);
39
40 if ($statusCode !== 200) {
41 $error = $responseBody['message'] ?? __('Something went wrong.', 'fluent-support');
42 return new \WP_Error($statusCode, $error);
43 }
44
45 $content = $responseBody['response'] ?? '';
46
47 if (empty($content)) {
48 return new \WP_Error('fluent_bot_error', __('No AI response found in the API response.', 'fluent-support'));
49 }
50
51 $tokenUsage = $responseBody['token_usage'] ?? [];
52 $totalTokens = ($tokenUsage['input_tokens'] ?? 0) + ($tokenUsage['output_tokens'] ?? 0);
53 do_action('fluent_support/ai_response_success', $ticketId, $prompt, $totalTokens, "FluentBot");
54
55 // Return both content and chat_id if available
56 return [
57 'content' => $content,
58 'chat_id' => $responseBody['chat_id'] ?? null
59 ];
60 }
61
62 public function makeStreamRequest(int $ticketId, $prompt, array $args = [])
63 {
64 $timeout = apply_filters('fs_ai_request_timeout', 120);
65
66 // Use cURL for streaming
67 // Note: Using cURL here because WordPress HTTP API doesn't support streaming SSE responses
68 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init
69 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt
70 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec
71 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close
72 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_getinfo
73 // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_error
74 // PluginCheck:ignoreFile
75 $ch = curl_init();
76 curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
77 curl_setopt($ch, CURLOPT_POST, true);
78 curl_setopt($ch, CURLOPT_POSTFIELDS, wp_json_encode($args));
79 curl_setopt($ch, CURLOPT_HTTPHEADER, [
80 'Content-Type: application/json',
81 ]);
82
83 $buffer = '';
84 $conversationId = null;
85 $streamTokens = 0;
86
87 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$buffer, &$conversationId, &$streamTokens) {
88 $buffer .= $data;
89
90 // Process complete SSE events from the AI API
91 $events = explode("\n\n", $buffer);
92 $buffer = array_pop($events); // Keep incomplete event in buffer
93
94 foreach ($events as $event) {
95 if (trim($event)) {
96 $lines = explode("\n", $event);
97 $eventType = '';
98 $eventId = '';
99 $eventDataLines = [];
100
101 foreach ($lines as $line) {
102 if (strpos($line, 'event: ') === 0) {
103 $eventType = trim((string)substr($line, 7));
104 } elseif (strpos($line, 'id: ') === 0) {
105 $eventId = trim((string)substr($line, 4));
106 } elseif (strpos($line, 'data: ') === 0) {
107 $eventDataLines[] = (string)substr($line, 6);
108 }
109 }
110
111 // Forward the event to the browser with proper formatting
112 if ($eventType) {
113 echo "event: ".esc_html($eventType)."\n";
114
115 // Include ID if present
116 if ($eventId !== '') {
117 echo "id: ".esc_html($eventId)."\n";
118 }
119
120 // Handle multiple data lines properly
121 if (!empty($eventDataLines)) {
122 foreach ($eventDataLines as $dataLine) {
123 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw SSE payload from trusted bot endpoint; rendered output sanitized client-side
124 echo "data: ".$dataLine."\n";
125 }
126 } else {
127 echo "data: \n";
128 }
129
130 echo "\n";
131
132 // Store chat_id for later use
133 if ($eventType === 'chat_id' && !empty($eventDataLines)) {
134 $conversationId = $eventDataLines[0];
135 } elseif ($eventType === 'token_usage' && !empty($eventDataLines)) {
136 $usage = json_decode($eventDataLines[0], true);
137 if (is_array($usage)) {
138 $streamTokens = ($usage['input_tokens'] ?? 0) + ($usage['output_tokens'] ?? 0);
139 }
140 }
141
142 flush();
143 }
144 }
145 }
146
147 return strlen($data);
148 });
149
150 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
151 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
152 curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // Smaller buffer for faster streaming
153
154 $result = curl_exec($ch);
155 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
156
157 if (curl_error($ch)) {
158 echo "event: error\n";
159 echo "data: " . json_encode(['error' => curl_error($ch)]) . "\n\n";
160 flush();
161 }
162
163 curl_close($ch);
164
165 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init
166 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_setopt
167 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_exec
168 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_close
169 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_getinfo
170 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_error
171
172 if ($httpCode === 200) {
173 do_action('fluent_support/ai_response_success', $ticketId, $prompt, $streamTokens, "FluentBot");
174 }
175 }
176
177 protected function sendRequest(array $payload)
178 {
179 $headers = [
180 'Content-Type' => 'application/json',
181 ];
182
183 $timeout = apply_filters('fs_ai_request_timeout', 60);
184
185 return wp_remote_post($this->apiUrl, [
186 'headers' => $headers,
187 'body' => wp_json_encode($payload),
188 'timeout' => $timeout,
189 ]);
190 }
191 }
192