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\PromptCommunicationStyle;
7use App\Http\Models\Prompts\PromptModel;
8use App\Http\Models\Prompts\PromptSetting;
9use App\Http\Models\Prompts\PromptType;
10use App\Http\Models\PromptTone;
11use App\Http\Models\UserPersona;
12use App\Services\FlyMsgAI\GoogleTranslate;
13use App\Services\FlyMsgAI\GeminiAPI;
14use Exception;
15use Illuminate\Support\Facades\Log;
16use GuzzleHttp\Client;
17
18class 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}