Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 457
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
FlyAIController
0.00% covered (danger)
0.00%
0 / 457
0.00% covered (danger)
0.00%
0 / 12
4160
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkSentences
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 rewrite
0.00% covered (danger)
0.00%
0 / 42
0.00% covered (danger)
0.00%
0 / 1
56
 checkQuota
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
110
 engage_generate
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 1
42
 post_generate
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 1
110
 save_custom_prompt
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
12
 update_custom_prompt
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
6
 delete_custom_prompt
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
12
 getTypingSpeedConfigByFeature
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
132
 convertStringToJson
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 youtube
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace App\Http\Controllers\v2;
4
5use App\Helpers\Constants;
6use Illuminate\Http\Request;
7use App\Traits\SubscriptionTrait;
8use Illuminate\Http\JsonResponse;
9use App\Http\Controllers\Controller;
10use App\Events\TrackFlyMsgAIUsageEvent;
11use App\Helpers\FlyMSGLogger;
12use App\Http\Models\FlyMsgAI\SavedPrompt;
13use App\Http\Models\Parameter;
14use App\Http\Models\PromptCompanyNewUpdate;
15use App\Http\Models\PromptLanguage;
16use App\Http\Models\PromptLengthOfPost;
17use App\Http\Models\PromptPersonalMilestone;
18use App\Http\Models\Prompts\CustomPrompts;
19use App\Http\Models\Prompts\PromptModel;
20use App\Http\Models\Prompts\PromptSetting;
21use App\Http\Models\Prompts\PromptType;
22use App\Http\Models\PromptTone;
23use App\Http\Models\Setting;
24use App\Http\Models\UserPersona;
25use App\Http\Requests\FlyAI\EngageGenerateFormRequest;
26use App\Http\Requests\FlyAI\PostGenerateFormRequest;
27use App\Http\Requests\FlyAI\RewriteFormRequest;
28use App\Http\Requests\FlyAI\SavePromptFormRequest;
29use App\Services\FlyMsgAI\FlyMsgAIService;
30use App\Services\FlyMsgAI\GeminiAPI;
31use Carbon\Carbon;
32use Illuminate\Http\Response;
33use Illuminate\Support\Str;
34use stdClass;
35
36class FlyAIController extends Controller
37{
38    use SubscriptionTrait;
39
40    public function __construct(
41        private readonly FlyMsgAIService $ai_service
42    ) {}
43
44    public function checkSentences(Request $request): JsonResponse
45    {
46        $sentences = $request->sentences;
47
48        if (!$sentences) {
49            return response()->json([
50                'status' => 'error',
51                'message' => 'sentences field is required.',
52            ], 400);
53        }
54
55        $rewrite = $this->ai_service->checkSentences($sentences);
56
57        return response()->json([
58            'status' => 'success',
59            'data' => $rewrite,
60        ]);
61    }
62
63    public function rewrite(RewriteFormRequest $request): JsonResponse
64    {
65        $data = $request->validated();
66        $user = $request->user();
67
68        $rewrite = $this->ai_service->rewrite($data);
69
70        $prompt = "FlyMSG AI Rewrite: " . $data['action'] . "\n" . "Input: " . $data['input'];
71        if (isset($data['tone']) && !empty($data['tone'])) {
72            $tone = PromptTone::find($data['tone']);
73            if ($tone) {
74                $prompt .= "\nTone: " . $tone->name;
75            }
76        }
77        if (isset($data['language']) && !empty($data['language'])) {
78            $language = PromptLanguage::find($data['language']);
79            if ($language) {
80                $prompt .= "\nLanguage: " . $language->name;
81            }
82        }
83
84        $quota = $this->getFlyAIQuota($user);
85
86        TrackFlyMsgAIUsageEvent::dispatch(
87            $user,
88            $rewrite['output']['suggestion'],
89            $request->browser,
90            $data['action'],
91            $rewrite['prompt'],
92            $data['product'],
93            $user->id . '-' . Carbon::now()->timestamp,
94            [
95                'response' => $rewrite['output'],
96                'prompt' => $rewrite['prompt'],
97            ],
98            $data['context'] ?? ''
99        );
100
101        $usedQuota = ($quota['used'] ?? 0) + 1;
102        $totalQuota = $quota['total'] ?? 0;
103
104        return response()->json([
105            'status' => 'success',
106            'data' => [
107                'input' => $data,
108                'output' => $rewrite['output'],
109                'quota' => [
110                    'used' => $usedQuota,
111                    'total' => $totalQuota,
112                    'remaining' => max($totalQuota - $usedQuota, 0),
113                    'seconds_remaining_until_next_prompt_refill' => now()->diffInSeconds(now()->startOfDay()->addDay())
114                ],
115            ],
116        ]);
117    }
118
119    public function checkQuota(Request $request): JsonResponse
120    {
121        $feature = $request->feature;
122
123        if (!$feature) {
124            return response()->json([
125                'status' => 'error',
126                'message' => 'Feature is required.',
127            ], 400);
128        }
129
130        $user = $request->user();
131
132        $order = [];
133
134        $model = PromptModel::where('is_active', true)->latest()->first();
135        $setting = PromptSetting::where('prompt_model_id', $model->id)->where('is_active', true)->where('feature', $feature)->latest()->first();
136        $prompts = PromptType::where('prompt_setting_id', $setting->id)
137            ->where('is_active', true)
138            ->where('feature', $feature)
139            ->get();
140
141        $default_prompts = [];
142        foreach ($prompts as $prompt) {
143            $default_prompts[] = [
144                'id' => $prompt->id,
145                'name' => $prompt->name,
146                'prompt' => $prompt->mission ?? '',
147                'feature' => $feature,
148                'prompt_tone_id' => $prompt->prompt_tone_id,
149            ];
150        }
151
152        if (str_contains($feature, 'flyengage')) {
153            $order = ['curious', 'optimistic', 'thoughtful', 'custom'];
154        } elseif ($feature == "flypost") {
155            $order = ['thought leadership', 'company news', 'celebrate something', 'hiring', 'custom'];
156        }
157
158        $default_prompts = collect($default_prompts)->sort(function ($a, $b) use ($order) {
159            $posA = array_search($a['name'], $order);
160            $posB = array_search($b['name'], $order);
161
162            $posA = $posA === false ? count($order) : $posA;
163            $posB = $posB === false ? count($order) : $posB;
164
165            return $posA - $posB;
166        });
167
168        $default_prompts = $default_prompts->values();
169
170        $quota = $this->getFlyAIQuota($user);
171        $promptsQuota = $this->getPromptQuota($user);
172        $savedPrompts = CustomPrompts::where('feature', $feature)->get();
173
174        $plan = $this->getCurrentPlan($user);
175        $show_upgrade_button = empty($user->company_id) && $plan->identifier !== 'sales-pro-monthly' && $plan->identifier !== 'sales-pro-yearly';
176
177        return response()->json([
178            'status' => 'success',
179            'data' => [
180                'is_enterprise' => !empty($user->company_id) && $user->status !== 'Invited',
181                'show_upgrade_button' => $show_upgrade_button,
182                'quota' => $quota,
183                'prompts_quota' => $promptsQuota,
184                'default_prompts' => $default_prompts,
185                'saved_prompts' => $savedPrompts,
186            ],
187        ]);
188    }
189
190    public function engage_generate(EngageGenerateFormRequest $request): JsonResponse
191    {
192        $user = $request->user();
193        $validated = $request->validated();
194
195        $prompt_id = $validated['prompt_id'];
196        $custom_prompt_id = $validated['custom_prompt_id'] ?? null;
197        $isReply = $validated['is_reply'] ?? false;
198        $context = $validated['context'];
199        $is_regenerate = $validated['is_regenerate'] ?? false;
200        $uniqueId = $validated['uniqueId'] ?? null;
201        $include_hashtags = $validated['include_hashtags'];
202        $include_emojis = $validated['include_emojis'];
203        $persona_id = $validated['persona_id'] ?? null;
204        $tone_id = $validated['tone_id'] ?? null;
205        $additional_instructions = $validated['additional_instructions'] ?? null;
206
207        $currentSubscriptionPlan = $this->getCurrentPlan($user);
208        $total = Constants::CURRENT_SUBSCRIPTION_PLAN_IDENTIFIERS[$currentSubscriptionPlan->identifier];
209        $used = $this->getQuotaUsed($user->id);
210
211        if (!empty($custom_prompt_id)) {
212            $customPrompt = CustomPrompts::find($custom_prompt_id);
213        }
214        $prompt = PromptType::find($prompt_id);
215        if (!empty($tone_id)) {
216            $tone = PromptTone::find($tone_id);
217        }
218
219        if (!empty($persona_id)) {
220            $persona = UserPersona::where('_id', $persona_id)->first();
221            if ($persona) {
222                $tone = $persona->prompt_tone;
223            }
224        }
225
226        $context['post']['content'] = strip_tags($context['post']['content']);
227
228        $uniqueId = "https://www.linkedin.com/feed/update/{$uniqueId}";
229
230        $name = strtolower($prompt->name);
231
232        try {
233            $aiResult = $this->ai_service->engageGenerate(
234                $name,
235                $context,
236                $uniqueId,
237                $user,
238                $include_hashtags,
239                $include_emojis,
240                $persona ?? null,
241                $tone ?? null,
242                $additional_instructions ?? null,
243                $customPrompt ?? null,
244                $is_regenerate,
245                $isReply
246            );
247            $prompt_response = $this->ai_service->transform($aiResult['response']);
248
249            TrackFlyMsgAIUsageEvent::dispatch(
250                $user,
251                $prompt_response,
252                $request->browser,
253                $prompt->name,
254                $prompt->mission . "\n" . $additional_instructions . "\n\n Post:\n\n " . $context['post']['content'],
255                'flyengage',
256                $uniqueId,
257                $aiResult,
258                $context
259            );
260
261            $used++;
262
263            $quota = [
264                'used' => $used,
265                'total' => $total,
266                'remaining' => max($total - $used, 0),
267                'deleted' => $used >= $total,
268            ];
269
270            $words_per_minute_typing_speed_control = $this->getTypingSpeedConfigByFeature('flyengage', $user);
271
272            return response()->json([
273                'status' => 'success',
274                'data' => [
275                    'prompt' => $prompt->mission . "\n" . $additional_instructions . "\n\n Post:\n\n ",
276                    'context' => $context,
277                    'prompt_response' => $prompt_response,
278                    'quota' => $quota,
279                    'typingConfig' => $words_per_minute_typing_speed_control,
280                ],
281            ]);
282        } catch (\Throwable $th) {
283            FlyMSGLogger::logError("generate", $th);
284            return response()->json([
285                "status" => "error",
286                "data" => [
287                    "message" => "Something went wrong. " . $th->getMessage()
288                ]
289            ], Response::HTTP_INTERNAL_SERVER_ERROR);
290        }
291    }
292
293    public function post_generate(PostGenerateFormRequest $request): JsonResponse
294    {
295        $user = $request->user();
296        $validated = $request->validated();
297
298        $prompt_id = $validated['prompt_id'];
299        $custom_prompt_id = $validated['custom_prompt_id'] ?? null;
300        $persona_id = $validated['persona_id'] ?? null;
301        $topic = $validated['topic'] ?? null;
302        $insert_role = $validated['insert_role'] ?? null;
303        $prompt_company_new_update = $validated['prompt_company_new_update_id'] ?? null;
304        $prompt_personal_milestone = $validated['prompt_personal_milestone_id'] ?? null;
305        $tone_id = $validated['tone_id'] ?? null;
306        $prompt_language_id = $validated['prompt_language_id'] ?? null;
307        $length_of_post_id = $validated['length_of_post_id'] ?? null;
308        $youtube_url = $validated['youtube_url'] ?? null;
309        $blog_url = $validated['blog_url'] ?? null;
310        $include_hashtags = $validated['include_hashtags'];
311        $include_emojis = $validated['include_emojis'];
312        $additional_instructions = $validated['additional_instructions'] ?? null;
313        $is_regenerate = $validated['is_regenerate'] ?? false;
314        $uniqueId = $validated['uniqueId'] ?? null;
315
316        $currentSubscriptionPlan = $this->getCurrentPlan($user);
317        $total = Constants::CURRENT_SUBSCRIPTION_PLAN_IDENTIFIERS[$currentSubscriptionPlan->identifier];
318        $used = $this->getQuotaUsed($user->id);
319
320        if (!empty($custom_prompt_id)) {
321            $customPrompt = CustomPrompts::find($custom_prompt_id);
322        }
323        $prompt = PromptType::find($prompt_id);
324        if (!empty($tone_id)) {
325            $tone = PromptTone::find($tone_id);
326        }
327
328        // company news
329        if (!empty($prompt_company_new_update)) {
330            $promptCompanyNewUpdate = PromptCompanyNewUpdate::find($prompt_company_new_update);
331        }
332        // celebrate something
333        if (!empty($prompt_personal_milestone)) {
334            $promptPersonalMilestone = PromptPersonalMilestone::find($prompt_personal_milestone);
335        }
336
337        if (!empty($length_of_post_id)) {
338            $lengthOfPost = PromptLengthOfPost::find($length_of_post_id);
339        }
340
341        if (!empty($prompt_language_id)) {
342            $promptLanguage = PromptLanguage::find($prompt_language_id);
343        }
344
345        if (!empty($persona_id)) {
346            $persona = UserPersona::where('_id', $persona_id)->first();
347            if ($persona) {
348                $tone = $persona->prompt_tone;
349            }
350        }
351
352        $uniqueId = str_slug($request->uniqueId);
353
354        $name = strtolower($prompt->name);
355
356        try {
357            $aiResult = $this->ai_service->postGenerate(
358                $name,
359                $youtube_url,
360                $blog_url,
361                $uniqueId,
362                $user,
363                $include_hashtags,
364                $include_emojis,
365                $promptLanguage,
366                $lengthOfPost,
367                $topic,
368                $insert_role,
369                $promptCompanyNewUpdate ?? null,
370                $promptPersonalMilestone ?? null,
371                $persona ?? null,
372                $tone ?? null,
373                $additional_instructions ?? null,
374                $customPrompt ?? null,
375                $is_regenerate
376            );
377            $prompt_response = $this->ai_service->transform($aiResult['response']);
378
379            TrackFlyMsgAIUsageEvent::dispatch(
380                $user,
381                $prompt_response,
382                $request->browser,
383                $prompt->name,
384                $prompt->mission . "\n" . $additional_instructions,
385                'flypost',
386                $uniqueId,
387                $aiResult,
388                null
389            );
390
391            $used++;
392
393            $quota = [
394                'used' => $used,
395                'total' => $total,
396                'remaining' => max($total - $used, 0),
397                'deleted' => $used >= $total,
398            ];
399
400            $words_per_minute_typing_speed_control = $this->getTypingSpeedConfigByFeature('flypost', $user);
401
402            return response()->json([
403                'status' => 'success',
404                'data' => [
405                    'prompt' => $prompt->mission . "\n" . $additional_instructions,
406                    'prompt_response' => $prompt_response,
407                    'quota' => $quota,
408                    'typingConfig' => $words_per_minute_typing_speed_control,
409                ],
410            ]);
411        } catch (\Throwable $th) {
412            FlyMSGLogger::logError("generate", $th);
413            return response()->json([
414                "status" => "error",
415                "data" => [
416                    "message" => "Something went wrong. " . $th->getMessage()
417                ]
418            ], Response::HTTP_INTERNAL_SERVER_ERROR);
419        }
420    }
421
422    public function save_custom_prompt(SavePromptFormRequest $request): JsonResponse
423    {
424        $validated = $request->validated();
425        $user = $request->user();
426
427        $existsSameName = CustomPrompts::where('name', $validated['name'])->where('feature', $validated['feature'])->where('user_id', $user->id)->exists();
428
429        if ($existsSameName) {
430            return response()->json([
431                'status' => 'error',
432                'message' => 'You already have a custom prompt with the same name',
433            ], 400);
434        }
435
436        $existsDefaultSameName = PromptType::where('is_active', true)->where('name', $validated['name'])->where('feature', $validated['feature'])->exists();
437
438        if ($existsDefaultSameName) {
439            return response()->json([
440                'status' => 'error',
441                'message' => 'You cannot use the same name as a default prompt',
442            ], 400);
443        }
444
445        $customPrompt = new CustomPrompts();
446        $customPrompt->user_id = $user->id;
447        $customPrompt->fill($validated);
448        $customPrompt->save();
449
450        $customPrompt = $customPrompt->fresh();
451
452        $quota = $this->getFlyAIQuota($user);
453        $promptsQuota = $this->getPromptQuota($user);
454        $savedPrompts = CustomPrompts::where('feature', $validated['feature'])->get();
455
456        return response()->json([
457            'status' => 'success',
458            'data' => [
459                'success' => true,
460                'prompt' => $customPrompt,
461                'quota' => $quota,
462                'prompts_quota' => $promptsQuota,
463                'saved_prompts' => $savedPrompts,
464            ],
465        ]);
466    }
467
468    public function update_custom_prompt(SavePromptFormRequest $request, CustomPrompts $customPrompt): JsonResponse
469    {
470        $validated = $request->validated();
471        $user = $request->user();
472
473        $existsSameName = CustomPrompts::where('name', $validated['name'])
474            ->where('feature', $validated['feature'])
475            ->where('user_id', $user->id)
476            ->where('_id', '!=', $customPrompt->id)
477            ->exists();
478
479        if ($existsSameName) {
480            return response()->json([
481                'status' => 'error',
482                'message' => 'You already have a custom prompt with the same name',
483            ], 400);
484        }
485
486        $customPrompt->user_id = $user->id;
487        $customPrompt->fill($validated);
488        $customPrompt->save();
489
490        $customPrompt = $customPrompt->fresh();
491
492        $quota = $this->getFlyAIQuota($user);
493        $promptsQuota = $this->getPromptQuota($user);
494        $savedPrompts = CustomPrompts::where('feature', $validated['feature'])->get();
495
496        return response()->json([
497            'status' => 'success',
498            'data' => [
499                'success' => true,
500                'prompt' => $customPrompt,
501                'quota' => $quota,
502                'prompts_quota' => $promptsQuota,
503                'saved_prompts' => $savedPrompts,
504            ],
505        ]);
506    }
507
508    public function delete_custom_prompt(Request $request, CustomPrompts $customPrompt): JsonResponse
509    {
510        $user = $request->user();
511        $customPrompt->user_id = $user->id;
512        $feature = $customPrompt->feature;
513
514        if ($user->id != $customPrompt->user_id) {
515            return response()->json([
516                'status' => 'error',
517                'message' => 'You are not authorized to delete this custom prompt',
518            ], 403);
519        }
520
521        if (!$customPrompt->delete()) {
522            return response()->json([
523                'status' => 'error',
524                'message' => 'Failed to delete custom prompt',
525            ], 400);
526        }
527
528        $quota = $this->getFlyAIQuota($user);
529        $promptsQuota = $this->getPromptQuota($user);
530        $savedPrompts = CustomPrompts::where('feature', $feature)->get();
531
532        return response()->json([
533            'status' => 'success',
534            'data' => [
535                'message' => 'Custom prompt deleted successfully',
536                'success' => true,
537                'quota' => $quota,
538                'prompts_quota' => $promptsQuota,
539                'saved_prompts' => $savedPrompts,
540            ],
541        ]);
542    }
543
544    private function getTypingSpeedConfigByFeature($feature, $user)
545    {
546        $parameters = Parameter::where('name', 'like', 'flywrite_typing%')->get()->keyBy('name');
547
548        $flypostTypingMode = $parameters['flywrite_typing_post_typing_mode']->value ?? 'all';
549        $flypostTypingSpeed = $parameters['flywrite_typing_post_typing_speed']->value ?? Constants::FLYPOST_FE_TYPING_SPEED_PER_MINUTE;
550        $flyengageTypingMode = $parameters['flywrite_typing_engage_typing_mode']->value ?? 'all';
551        $flyengageTypingSpeed = $parameters['flywrite_typing_engage_typing_speed']->value ?? Constants::FLYENGAGE_FE_TYPING_SPEED_PER_MINUTE;
552
553        $userSetting = Setting::where('user_id', $user->id)->first();
554
555        $flyPostType = $userSetting?->typing_style ?? $flypostTypingMode;
556        $flyEngageType = $userSetting?->typing_style ?? $flyengageTypingMode;
557        $flyPostSpeed = (!empty($userSetting?->typing_style) && $userSetting?->typing_style == 'letter') ? ($userSetting->typing_speed ?? $flypostTypingSpeed) : $flypostTypingSpeed;
558        $flyEngageSpeed = (!empty($userSetting?->typing_style) && $userSetting?->typing_style == 'letter') ? ($userSetting->typing_speed ?? $flyengageTypingSpeed) : $flyengageTypingSpeed;
559
560        if ($user->company_id) {
561            $companySetting = Setting::where('company_id', $user->company_id)->first();
562
563            if ($companySetting) {
564                if ($companySetting->override_user_typing_style) {
565                    if (!empty($companySetting->typing_style)) {
566                        $flyPostType = $companySetting->typing_style;
567                        $flyEngageType = $companySetting->typing_style;
568
569                        if ($companySetting->typing_style == 'letter' && !empty($companySetting->typing_speed)) {
570                            $flyPostSpeed = $companySetting->typing_speed;
571                            $flyEngageSpeed = $companySetting->typing_speed;
572                        }
573                    }
574                }
575            }
576        }
577
578        return match ($feature) {
579            "flypost" => [
580                "type" => $flyPostType,
581                "words_per_minute" => $flyPostSpeed
582            ],
583            "flyengage" => [
584                "type" => $flyEngageType,
585                "words_per_minute" => $flyEngageSpeed
586            ],
587            default => null
588        };
589    }
590
591    function convertStringToJson(string $input)
592    {
593        $cleanedInput = Str::of($input)
594            ->replaceMatches('/^```json/m', '')
595            ->replaceMatches('/```$/m', '')
596            ->replaceMatches('/^```text/m', '')
597            ->replaceMatches('/```$/m', '')
598            ->trim();
599
600        try {
601            return json_decode($cleanedInput, true, 512, JSON_THROW_ON_ERROR);
602        } catch (\Exception $e) {
603            return $cleanedInput;
604        }
605    }
606
607    public function youtube(Request $request): JsonResponse
608    {
609        $access_token = GeminiAPI::getAIAccessToken();
610        $youtube_url = $request->input('youtube_url');
611        $prompt = $request->input('prompt');
612        $model = $request->input('model', 'gemini-2.5-flash:streamGenerateContent');
613        $temperature = $request->input('temperature', 1);
614        $top_p = $request->input('top_p', 0.95);
615
616        $geminiAPI = new GeminiAPI($access_token);
617
618        $data = [
619            "contents" => [
620                "role" => "user",
621                "parts" => [
622                    [
623                        "text" => $prompt,
624                    ],
625                    [
626                        "fileData" => [
627                            "mimeType" => "video/*",
628                            "fileUri" => $youtube_url,
629                        ]
630                    ]
631                ]
632            ],
633            "generationConfig" => [
634                "maxOutputTokens" => 8000,
635                "temperature" => $temperature,
636                "topP" => $top_p,
637            ]
638        ];
639
640        try {
641
642            $response = $geminiAPI->postCompletions($data, $model);
643            $responseData = json_decode($response->getBody()->getContents(), true);
644
645            $extractedText = '';
646
647            foreach ($responseData as $message) {
648                foreach ($message['candidates'] as $candidate) {
649                    if (isset($candidate['content'])) {
650                        foreach ($candidate['content']['parts'] as $part) {
651                            $extractedText .= $part['text'];
652                        }
653                    }
654                }
655            }
656        } catch (\Exception $e) {
657            // split after `response:` and parse the JSON
658            $result = explode('response:', $e->getMessage(), 2);
659            if (count($result) > 1) {
660                $jsonPart = $result[1];
661                $extractedText = $this->convertStringToJson($jsonPart);
662            } else {
663                $extractedText = 'Error: ' . $e->getMessage();
664            }
665
666            return response()->json([
667                'status' => 'error',
668                'message' => $extractedText,
669            ], 500);
670        }
671
672        return response()->json($this->convertStringToJson($extractedText));
673    }
674}