Passed
Push — EXTRACT_CLASSES ( 231cec...0382f2 )
by Rafael
65:54 queued 05:18
created

Ai::generateContent()   F

Complexity

Conditions 33
Paths 16471

Size

Total Lines 136
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 33
eloc 82
nc 16471
nop 4
dl 0
loc 136
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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
/* Copyright (C) 2024       Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2024       Frédéric France             <[email protected]>
5
 * Copyright (C) 2024       Rafael San José             <[email protected]>
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19
 * or see https://www.gnu.org/
20
 */
21
22
namespace Dolibarr\Code\Ai\Classes;
23
24
/**
25
 * \file    htdocs/ai/class/ai.class.php
26
 * \ingroup ai
27
 * \brief   Class files with common methods for Ai
28
 */
29
30
require_once DOL_DOCUMENT_ROOT . "/core/lib/admin.lib.php";
31
require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/geturl.lib.php';
32
33
34
/**
35
 * Class for AI
36
 */
37
class Ai
38
{
39
    /**
40
     * @var DoliDB $db Database object
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Ai\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
41
     */
42
    protected $db;
43
44
    /**
45
     * @var string $apiService
46
     */
47
    private $apiService;
48
49
    /**
50
     * @var string $apiKey
51
     */
52
    private $apiKey;
53
54
    /**
55
     * @var string $apiEndpoint
56
     */
57
    private $apiEndpoint;
58
59
60
    /**
61
     * Constructor
62
     *
63
     * @param   DoliDB  $db      Database handler
64
     *
65
     */
66
    public function __construct($db)
67
    {
68
        $this->db = $db;
69
70
        // Get API key according to enabled AI
71
        $this->apiService = getDolGlobalString('AI_API_SERVICE', 'chatgpt');
72
        $this->apiKey = getDolGlobalString('AI_API_' . strtoupper($this->apiService) . '_KEY');
73
    }
74
75
    /**
76
     * Generate response of instructions
77
     *
78
     * @param   string      $instructions   Instruction to generate content
79
     * @param   string      $model          Model name ('gpt-3.5-turbo', 'gpt-4-turbo', 'dall-e-3', ...)
80
     * @param   string      $function       Code of the feature we want to use ('textgeneration', 'transcription', 'audiotext', 'imagegeneration', 'translation')
81
     * @param   string      $format         Format for output ('', 'html', ...)
82
     * @return  mixed       $response
83
     */
84
    public function generateContent($instructions, $model = 'auto', $function = 'textgeneration', $format = '')
85
    {
86
        if (empty($this->apiKey)) {
87
            return array('error' => true, 'message' => 'API key is not defined for the AI enabled service ' . $this->apiService);
88
        }
89
90
        if (empty($this->apiEndpoint)) {
91
            if ($function == 'imagegeneration') {
92
                if ($this->apiService == 'chatgpt') {
93
                    $this->apiEndpoint = 'https://api.openai.com/v1/images/generations';
94
                    if ($model == 'auto') {
95
                        $model = getDolGlobalString('AI_API_CHATGPT_MODEL_IMAGE', 'dall-e-3');
96
                    }
97
                }
98
            } elseif ($function == 'audiotext') {
99
                if ($this->apiService == 'chatgpt') {
100
                    $this->apiEndpoint = 'https://api.openai.com/v1/audio/speech';
101
                    if ($model == 'auto') {
102
                        $model = getDolGlobalString('AI_API_CHATGPT_MODEL_AUDIO', 'tts-1');
103
                    }
104
                }
105
            } elseif ($function == 'transcription') {
106
                if ($this->apiService == 'chatgpt') {
107
                    $this->apiEndpoint = 'https://api.openai.com/v1/audio/transcriptions';
108
                    if ($model == 'auto') {
109
                        $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSCRIPT', 'whisper-1');
110
                    }
111
                }
112
            } elseif ($function == 'translation') {
113
                if ($this->apiService == 'chatgpt') {
114
                    $this->apiEndpoint = 'https://api.openai.com/v1/audio/translations';
115
                    if ($model == 'auto') {
116
                        $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSLATE', 'whisper-1');
117
                    }
118
                }
119
            } else {    // else textgeneration...
120
                if ($this->apiService == 'groq') {
121
                    $this->apiEndpoint = 'https://api.groq.com/openai/v1/chat/completions';
122
                    if ($model == 'auto') {
123
                        $model = getDolGlobalString('AI_API_GROK_MODEL_TEXT', 'mixtral-8x7b-32768');    // 'llama3-8b-8192', 'gemma-7b-it'
124
                    }
125
                } elseif ($this->apiService == 'chatgpt') {
126
                    $this->apiEndpoint = 'https://api.openai.com/v1/chat/completions';
127
                    if ($model == 'auto') {
128
                        $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TEXT', 'gpt-3.5-turbo');
129
                    }
130
                }
131
            }
132
        }
133
134
        dol_syslog("Call API for apiEndpoint=" . $this->apiEndpoint . " apiKey=" . substr($this->apiKey, 0, 3) . '***********, model=' . $model);
135
136
        try {
137
            if (empty($this->apiEndpoint)) {
138
                throw new Exception('The AI service ' . $this->apiService . ' is not yet supported for the type of request ' . $function);
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Ai\Classes\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
139
            }
140
141
            $configurationsJson = getDolGlobalString('AI_CONFIGURATIONS_PROMPT');
142
            $configurations = json_decode($configurationsJson, true);
143
144
            $prePrompt = '';
145
            $postPrompt = '';
146
147
            if (isset($configurations[$function])) {
148
                if (isset($configurations[$function]['prePrompt'])) {
149
                    $prePrompt = $configurations[$function]['prePrompt'];
150
                }
151
152
                if (isset($configurations[$function]['postPrompt'])) {
153
                    $postPrompt = $configurations[$function]['postPrompt'];
154
                }
155
            }
156
            $fullInstructions = $prePrompt . ' ' . $instructions . ' .' . $postPrompt;
157
158
159
            $payload = json_encode([
160
                'messages' => [
161
                    ['role' => 'user', 'content' => $fullInstructions]
162
                ],
163
                'model' => $model
164
            ]);
165
166
            $headers = ([
167
                'Authorization: Bearer ' . $this->apiKey,
168
                'Content-Type: application/json'
169
            ]);
170
            $response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers);
171
172
            if (empty($response['http_code'])) {
173
                throw new Exception('API request failed. No http received');
174
            }
175
            if (!empty($response['http_code']) && $response['http_code'] != 200) {
176
                throw new Exception('API request failed with status code ' . $response['http_code']);
177
            }
178
            // Decode JSON response
179
            $decodedResponse = json_decode($response['content'], true);
180
181
            // Extraction content
182
            $generatedContent = $decodedResponse['choices'][0]['message']['content'];
183
184
            dol_syslog("generatedContent=" . $generatedContent);
185
186
            // If content is not HTML, we convert it into HTML
187
            if ($format == 'html') {
188
                if (!dol_textishtml($generatedContent)) {
189
                    dol_syslog("Result was detected as not HTML so we convert it into HTML.");
190
                    $generatedContent = dol_nl2br($generatedContent);
191
                } else {
192
                    dol_syslog("Result was detected as already HTML. Do nothing.");
193
                }
194
195
                // TODO If content is for website module, we must
196
                // - clan html header, keep body only and remove ``` ticks added by AI
197
                // - add tags <section contenEditable="true"> </section>
198
            }
199
200
            return $generatedContent;
201
        } catch (Exception $e) {
202
            $errormessage = $e->getMessage();
203
            if (!empty($response['content'])) {
204
                $decodedResponse = json_decode($response['content'], true);
205
206
                // With OpenAI, error is into an object error into the content
207
                if (!empty($decodedResponse['error']['message'])) {
208
                    $errormessage .= ' - ' . $decodedResponse['error']['message'];
209
                }
210
            }
211
212
            return array(
213
                'error' => true,
214
                'message' => $errormessage,
215
                'code' => (empty($response['http_code']) ? 0 : $response['http_code']),
216
                'curl_error_no' => (empty($response['curl_error_no']) ? $response['curl_error_no'] : ''),
217
                'format' => $format,
218
                'service' => $this->apiService,
219
                'function' => $function
220
            );
221
        }
222
    }
223
}
224