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

190 lines 7.0 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 $apiKey;
10 protected $apiUrl;
11
12 public function __construct(?string $apiKey, string $apiUrl)
13 {
14 $this->apiKey = $apiKey;
15 $this->apiUrl = $apiUrl;
16 }
17
18 public function makeRequest(int $ticketId, $prompt, array $args = [])
19 {
20 $response = $this->sendRequest($args);
21
22 if (is_wp_error($response)) {
23 $message = $response->get_error_message();
24 $code = $response->get_error_code();
25 return new \WP_Error($code, $message);
26 }
27
28 $responseBody = json_decode(wp_remote_retrieve_body($response), true) ?? [];
29
30 if (!$responseBody || !is_array($responseBody)) {
31 return new \WP_Error('fluent_bot_error', __('Invalid or empty response from API', 'fluent-support'));
32 }
33
34 if (!empty($responseBody['error'])) {
35 $message = $responseBody['error']['message'] ?? __('Unknown error occurred', 'fluent-support');
36 return new \WP_Error('fluent_bot_error', $message);
37 }
38
39 $statusCode = wp_remote_retrieve_response_code($response);
40
41 if ($statusCode !== 200) {
42 $error = $responseBody['message'] ?? __('Something went wrong.', 'fluent-support');
43 return new \WP_Error($statusCode, $error);
44 }
45
46 $content = $responseBody['response'] ?? '';
47
48 if (empty($content)) {
49 return new \WP_Error('fluent_bot_error', __('No AI response found in the API response.', 'fluent-support'));
50 }
51
52 $totalTokens = $responseBody['token_usage']['total_tokens'] ?? $responseBody['totalTokens'] ?? 0;
53 do_action('fluent_support/ai_response_success', $ticketId, $prompt, $totalTokens, "Fluent Bot");
54
55 // Return both content and conversation_id if available
56 return [
57 'content' => $content,
58 'conversation_id' => $responseBody['conversation_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 !empty($this->apiKey) ? 'Authorization: Bearer ' . $this->apiKey : ''
82 ]);
83
84 $buffer = '';
85 $conversationId = null;
86
87 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use (&$buffer, &$conversationId) {
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(substr($line, 7));
104 } elseif (strpos($line, 'id: ') === 0) {
105 $eventId = trim(substr($line, 4));
106 } elseif (strpos($line, 'data: ') === 0) {
107 $eventDataLines[] = 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 echo "data: ".esc_html($dataLine)."\n";
124 }
125 } else {
126 echo "data: \n";
127 }
128
129 echo "\n";
130
131 // Store conversation_id for later use
132 if ($eventType === 'conversation_id' && !empty($eventDataLines)) {
133 $conversationId = $eventDataLines[0];
134 }
135
136 flush();
137 }
138 }
139 }
140
141 return strlen($data);
142 });
143
144 curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
145 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
146 curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); // Smaller buffer for faster streaming
147
148 $result = curl_exec($ch);
149 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
150
151 if (curl_error($ch)) {
152 echo "event: error\n";
153 echo "data: " . json_encode(['error' => curl_error($ch)]) . "\n\n";
154 flush();
155 }
156
157 curl_close($ch);
158
159 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init
160 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_setopt
161 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_exec
162 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_close
163 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_getinfo
164 // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_error
165
166 if ($httpCode === 200) {
167 do_action('fluent_support/ai_response_success', $ticketId, $prompt, 0, "Fluent Bot");
168 }
169 }
170
171 protected function sendRequest(array $payload)
172 {
173 $headers = [
174 'Content-Type' => 'application/json',
175 ];
176 // Add Authorization header only if API key is provided
177 if (!empty($this->apiKey)) {
178 $headers['Authorization'] = 'Bearer ' . $this->apiKey;
179 }
180
181 $timeout = apply_filters('fs_ai_request_timeout', 60);
182
183 return wp_remote_post($this->apiUrl, [
184 'headers' => $headers,
185 'body' => wp_json_encode($payload),
186 'timeout' => $timeout,
187 ]);
188 }
189 }
190