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