PluginProbe
Fluent Support – Helpdesk & Customer Support Ticket System / 2.3.1
Fluent Support – Helpdesk & Customer Support Ticket System v2.3.1
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.3.1, at app/Services/Integrations/FluentBot/FluentBotAPI.php

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