Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 175 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
UserPersonaService | |
0.00% |
0 / 175 |
|
0.00% |
0 / 11 |
1406 | |
0.00% |
0 / 1 |
getAll | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
create | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
setDefault | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
update | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
delete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
generateUserPersonaAIEmulation | |
0.00% |
0 / 81 |
|
0.00% |
0 / 1 |
272 | |||
sendRequestToGeminiAPI | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
parseGemini15Response | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
translateGeneratedPrompt | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
6 | |||
detectGeneratedAIResponseLanguage | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
translateRequestHeader | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace App\Http\Services; |
4 | |
5 | use App\Events\TrackFlyMsgAIUsageEvent; |
6 | use App\Http\Models\PromptCommunicationStyle; |
7 | use App\Http\Models\Prompts\PromptModel; |
8 | use App\Http\Models\Prompts\PromptSetting; |
9 | use App\Http\Models\Prompts\PromptType; |
10 | use App\Http\Models\PromptTone; |
11 | use App\Http\Models\UserPersona; |
12 | use App\Services\FlyMsgAI\GoogleTranslate; |
13 | use App\Services\FlyMsgAI\GeminiAPI; |
14 | use Exception; |
15 | use Illuminate\Support\Facades\Log; |
16 | use GuzzleHttp\Client; |
17 | |
18 | class UserPersonaService |
19 | { |
20 | /** |
21 | * @param int $itemsPerPage |
22 | * @param int $currentPage |
23 | * @param string $filter |
24 | * @param bool $disabled |
25 | * @return mixed |
26 | */ |
27 | public function getAll( |
28 | int $itemsPerPage, |
29 | int $currentPage, |
30 | string $filter, |
31 | bool $disabled |
32 | ) { |
33 | $query = UserPersona::query(); |
34 | |
35 | if (!empty($filter)) { |
36 | $query = $query->where('title', 'like', '%' . $filter . '%'); |
37 | } |
38 | |
39 | if (!$disabled) { |
40 | $query = $query->where('disabled', false)->orWhereNull('disabled'); |
41 | } |
42 | |
43 | return $query->get(); |
44 | } |
45 | |
46 | public function create(array $data) |
47 | { |
48 | $persona = UserPersona::create($data); |
49 | |
50 | if (!empty($data['is_default'])) { |
51 | UserPersona::where('_id', '!=', $persona->id) |
52 | ->where('user_id', $persona->user_id) |
53 | ->update(['is_default' => false]); |
54 | } |
55 | |
56 | return $persona; |
57 | } |
58 | |
59 | public function setDefault(string $userPersonaId, bool $value) |
60 | { |
61 | $persona = UserPersona::find($userPersonaId); |
62 | |
63 | $persona->is_default = $value; |
64 | |
65 | $persona->save(); |
66 | |
67 | UserPersona::where('_id', '!=', $userPersonaId) |
68 | ->where('user_id', $persona->user_id) |
69 | ->update(['is_default' => false]); |
70 | |
71 | return $persona; |
72 | } |
73 | |
74 | public function update(string $userPersonaId, array $data) |
75 | { |
76 | $persona = UserPersona::find($userPersonaId); |
77 | |
78 | $persona->fill($data); |
79 | |
80 | $persona->save(); |
81 | |
82 | if (!empty($data['is_default'])) { |
83 | UserPersona::where('_id', '!=', $userPersonaId) |
84 | ->where('user_id', $persona->user_id) |
85 | ->update(['is_default' => false]); |
86 | } |
87 | |
88 | return $persona; |
89 | } |
90 | |
91 | public function delete(UserPersona $userPersona) |
92 | { |
93 | return $userPersona->delete(); |
94 | } |
95 | |
96 | public function generateUserPersonaAIEmulation(array $data, $user, $regenerateCount) |
97 | { |
98 | $data['tone_of_voice'] = PromptTone::find($data['prompt_tone_id'])->prompt; |
99 | |
100 | $promptModel = PromptModel::where('is_active', true)->first(); |
101 | $promptSetting = PromptSetting::where('is_active', true)->where('prompt_model_id', $promptModel->id)->where('feature', 'flypersona')->first(); |
102 | $promptType = PromptType::where('is_active', true) |
103 | ->where('prompt_setting_id', $promptSetting->id) |
104 | ->where('feature', 'flypersona') |
105 | ->where('name', $data['type']) |
106 | ->first(); |
107 | |
108 | $step1 = $promptType->step_1_instruction; |
109 | $hasFieldsStep1 = false; |
110 | foreach ($promptType->step_1_steps as $step) { |
111 | $tempStep1 = "\n{$step['instructions']}"; |
112 | $hasFields = false; |
113 | foreach ($step['fields'] as $field) { |
114 | $hasFields = true; |
115 | $computedField = $data[$field]; |
116 | if (is_array($computedField)) { |
117 | $computedField = implode(", ", $computedField); |
118 | } |
119 | |
120 | if (!empty($computedField)) { |
121 | $hasFields = true; |
122 | $tempStep1 .= " {$computedField}"; |
123 | } |
124 | } |
125 | |
126 | if ($hasFields) { |
127 | $hasFieldsStep1 = true; |
128 | $step1 .= $tempStep1; |
129 | } |
130 | } |
131 | |
132 | if (!$hasFieldsStep1) { |
133 | $step1 = ""; |
134 | } |
135 | |
136 | $step2 = $promptType->step_2_instruction; |
137 | $hasFieldsStep2 = false; |
138 | foreach ($promptType->step_2_steps as $step) { |
139 | $tempStep2 = "\n{$step['instructions']}"; |
140 | $hasFields = false; |
141 | foreach ($step['fields'] as $field) { |
142 | $computedField = $data[$field]; |
143 | if (is_array($computedField)) { |
144 | $computedField = implode(", ", $computedField); |
145 | } |
146 | |
147 | if (!empty($computedField)) { |
148 | $hasFields = true; |
149 | $tempStep2 .= " {$computedField}"; |
150 | } |
151 | } |
152 | |
153 | if ($hasFields) { |
154 | $hasFieldsStep2 = true; |
155 | $step2 .= $tempStep2; |
156 | } |
157 | } |
158 | |
159 | if (!$hasFieldsStep2) { |
160 | $step2 = ""; |
161 | } |
162 | |
163 | $step3 = $promptType->step_3_instruction; |
164 | foreach ($promptType->step_3_steps as $step) { |
165 | $step3 .= "\n{$step}"; |
166 | } |
167 | |
168 | $step4 = $promptType->step_4_instruction; |
169 | foreach ($promptType->step_4_steps as $step) { |
170 | $step4 .= "\n\nInput:\n{$step['input']}"; |
171 | $step4 .= "\n\nOutput:\n{$step['output']}"; |
172 | } |
173 | |
174 | $prompt = "{$promptType->persona}\n\n{$step1}\n\n{$step2}\n\n{$step3}\n\n{$step4}\n\n{$promptType->output_instructions}"; |
175 | $language = $this->detectGeneratedAIResponseLanguage($prompt); |
176 | |
177 | $result = $this->sendRequestToGeminiAPI( |
178 | $prompt, |
179 | $language, |
180 | $promptSetting->output_token_limit, |
181 | $promptSetting->temperature, |
182 | $promptModel->name, |
183 | $promptSetting->top_p |
184 | ); |
185 | |
186 | Log::info("Generate Persona Prompt for user Id: {$data['user_id']}", [ |
187 | 'input' => $data, |
188 | 'prompt' => $prompt, |
189 | 'result' => $result |
190 | ]); |
191 | |
192 | if ($regenerateCount > 0) { |
193 | TrackFlyMsgAIUsageEvent::dispatch( |
194 | $user, |
195 | $result, |
196 | 'persona', |
197 | 'ai_emulation', |
198 | $prompt, |
199 | 'persona', |
200 | 'ai_emulation', |
201 | [ |
202 | 'response' => $result, |
203 | 'prompt' => $prompt, |
204 | ], |
205 | $data |
206 | ); |
207 | } |
208 | |
209 | return $result; |
210 | } |
211 | |
212 | private function sendRequestToGeminiAPI( |
213 | $prompt, |
214 | $language, |
215 | $max_tokens, |
216 | $temperature, |
217 | $model, |
218 | $topP |
219 | ) { |
220 | $access_token = GeminiAPI::getAIAccessToken(); |
221 | |
222 | $generationConfig = [ |
223 | "maxOutputTokens" => $max_tokens, |
224 | "temperature" => $temperature, |
225 | "topP" => $topP, |
226 | ]; |
227 | |
228 | $geminiAPI = new GeminiAPI($access_token); |
229 | // Gemini pro |
230 | $data = [ |
231 | "contents" => [ |
232 | "role" => "user", |
233 | "parts" => [ |
234 | "text" => $prompt, |
235 | ] |
236 | ], |
237 | "generationConfig" => $generationConfig, |
238 | ]; |
239 | $response = $geminiAPI->postCompletions($data, $model); |
240 | $responseData = json_decode($response->getBody()->getContents(), true); |
241 | $generatedResponse = $this->parseGemini15Response($responseData); |
242 | if ($language !== "en") { |
243 | $generatedResponse = $this->translateGeneratedPrompt($language, $generatedResponse); |
244 | } |
245 | return $generatedResponse; |
246 | } |
247 | |
248 | private function parseGemini15Response($response) |
249 | { |
250 | $extractedText = ''; |
251 | |
252 | foreach ($response as $message) { |
253 | foreach ($message['candidates'] as $candidate) { |
254 | if (isset($candidate['content'])) { |
255 | foreach ($candidate['content']['parts'] as $part) { |
256 | $extractedText .= $part['text']; |
257 | } |
258 | } |
259 | } |
260 | } |
261 | |
262 | return $extractedText; |
263 | } |
264 | |
265 | private function translateGeneratedPrompt(string $detectedLanguage, string $textToTranslate) |
266 | { |
267 | try { |
268 | $access_token = GoogleTranslate::getGoogleTranslateAccessToken(); |
269 | $client = new Client(); |
270 | |
271 | $baseURL = "https://translation.googleapis.com/v3/projects/project-romeo:translateText"; |
272 | |
273 | $data = [ |
274 | 'sourceLanguageCode' => 'en', |
275 | 'targetLanguageCode' => $detectedLanguage, |
276 | "contents" => [$textToTranslate], |
277 | "mimeType" => "text/plain" |
278 | ]; |
279 | |
280 | $response = $client->post($baseURL, [ |
281 | 'headers' => $this->translateRequestHeader($access_token), |
282 | 'json' => $data, |
283 | ]); |
284 | |
285 | $response = json_decode($response->getBody()->getContents(), true); |
286 | |
287 | return $response['translations'][0]['translatedText']; |
288 | } catch (Exception $e) { |
289 | Log::error($e); |
290 | return $textToTranslate; |
291 | } |
292 | } |
293 | |
294 | public function detectGeneratedAIResponseLanguage($textToBeDetected) |
295 | { |
296 | try { |
297 | $access_token = GoogleTranslate::getGoogleTranslateAccessToken(); |
298 | $client = new Client(); |
299 | $baseURL = "https://translate.googleapis.com/v3beta1/projects/project-romeo/locations/global:detectLanguage"; |
300 | $data = [ |
301 | 'content' => $textToBeDetected, |
302 | ]; |
303 | $response = $client->post($baseURL, [ |
304 | 'headers' => $this->translateRequestHeader($access_token), |
305 | 'json' => $data, |
306 | ]); |
307 | $response = json_decode($response->getBody()->getContents(), true); |
308 | return $response['languages'][0]['languageCode']; |
309 | } catch (Exception $e) { |
310 | Log::error($e->getMessage()); |
311 | return "en"; |
312 | } |
313 | } |
314 | |
315 | private function translateRequestHeader($access_token): array |
316 | { |
317 | return [ |
318 | "Authorization" => "Bearer $access_token", |
319 | "Content-Type" => "application/json", |
320 | ]; |
321 | } |
322 | } |