Passed
Pull Request — master (#7304)
by Yannick
09:41
created

GrokImageProvider::requestGrokAI()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 62
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 36
c 3
b 0
f 0
nc 15
nop 3
dl 0
loc 62
rs 8.7217

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Chamilo\CoreBundle\AiProvider;
6
7
use Chamilo\CoreBundle\Entity\AiRequests;
8
use Chamilo\CoreBundle\Repository\AiRequestsRepository;
9
use Chamilo\CoreBundle\Settings\SettingsManager;
10
use Exception;
11
use RuntimeException;
12
use Symfony\Bundle\SecurityBundle\Security;
13
use Symfony\Component\Security\Core\User\UserInterface;
14
use Symfony\Contracts\HttpClient\HttpClientInterface;
15
16
class GrokImageProvider implements AiImageProviderInterface
17
{
18
    private string $apiUrl;
19
    private string $apiKey;
20
    private string $model;
21
    private array $defaultOptions;
22
    private HttpClientInterface $httpClient;
23
    private AiRequestsRepository $aiRequestsRepository;
24
    private Security $security;
25
26
    public function __construct(
27
        HttpClientInterface $httpClient,
28
        SettingsManager $settingsManager,
29
        AiRequestsRepository $aiRequestsRepository,
30
        Security $security
31
    ) {
32
        $this->httpClient = $httpClient;
33
        $this->aiRequestsRepository = $aiRequestsRepository;
34
        $this->security = $security;
35
36
        // Get AI providers from settings
37
        $configJson = $settingsManager->getSetting('ai_helpers.ai_providers', true);
38
        $config = json_decode($configJson, true) ?? [];
39
40
        if (!isset($config['grok'])) {
41
            throw new RuntimeException('Grok configuration is missing.');
42
        }
43
        if (!isset($config['grok']['image'])) {
44
            throw new RuntimeException('Grok configuration for image processing is missing.');
45
        }
46
47
        $this->apiKey = $config['grok']['api_key'] ?? '';
48
        $this->apiUrl = $config['grok']['image']['url'] ?? 'https://api.x.ai/v1/images/generations';
49
        $this->model = $config['grok']['image']['model'] ?? 'grok-2-image';
50
        $this->defaultOptions = [
51
            'response_format' => $config['grok']['image']['response_format'] ?? 'b64_json',
52
            'n' => 1, // Number of images is fixed until we add an interface for choosing
53
        ];
54
55
        if (empty($this->apiKey)) {
56
            throw new RuntimeException('Grok API key is missing.');
57
        }
58
    }
59
60
    public function generateImage(string $prompt, string $toolName, ?array $options = []): ?string
61
    {
62
        return $this->requestGrokAI($prompt, $toolName, $options);
63
    }
64
    private function requestGrokAI(string $prompt, string $toolName, array $options = []): ?string
65
    {
66
        $userId = $this->getUserId();
67
        if (!$userId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $userId of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
68
            throw new RuntimeException('User not authenticated.');
69
        }
70
71
        $payload = [
72
            'model' => $this->model,
73
            'prompt' => $prompt,  // Direct prompt string, no messages array
74
            ...array_merge($this->defaultOptions, $options),
75
        ];
76
77
        try {
78
            $response = $this->httpClient->request('POST', $this->apiUrl, [
79
                'headers' => [
80
                    'Authorization' => 'Bearer '.$this->apiKey,
81
                    'Content-Type' => 'application/json',
82
                ],
83
                'json' => $payload,
84
            ]);
85
86
            $statusCode = $response->getStatusCode();
87
            if (200 !== $statusCode) {
88
                throw new RuntimeException('API request failed with status: ' . $statusCode);
89
            }
90
91
            $data = $response->toArray();
92
93
            // Check for error key first
94
            if (isset($data['error'])) {
95
                throw new RuntimeException('API error: ' . $data['error']['message']);
96
            }
97
98
            // Proper access: assuming response_format 'b64_json'
99
            if (isset($data['data'][0]['b64_json'])) {
100
                $generatedContent = $data['data'][0]['b64_json'];
101
102
                // Usage might not exist for images; default to 0
103
                $usage = $data['usage'] ?? ['prompt_tokens' => 0, 'completion_tokens' => 0, 'total_tokens' => 0];
104
105
                // Log request
106
                $aiRequest = new AiRequests();
107
                $aiRequest->setUserId($userId)
108
                    ->setToolName($toolName)
109
                    ->setRequestText($prompt)
110
                    ->setPromptTokens($usage['prompt_tokens'])
111
                    ->setCompletionTokens($usage['completion_tokens'])
112
                    ->setTotalTokens($usage['total_tokens'])
113
                    ->setAiProvider('grok')
114
                ;
115
116
                $this->aiRequestsRepository->save($aiRequest);
117
118
                return $generatedContent;
119
            }
120
121
            return null;
122
        } catch (Exception $e) {
123
            error_log('[AI][Grok] Exception: '.$e->getMessage());
124
125
            return null;
126
        }
127
    }
128
129
    private function getUserId(): ?int
130
    {
131
        $user = $this->security->getUser();
132
133
        return $user instanceof UserInterface ? $user->getId() : null;
134
    }
135
}
136