dolibarr 25.0.0-alpha
ajaxuploadpage.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2005-2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 *
6 * This file is a modified version of datepicker.php from phpBSM to fix some
7 * bugs, to add new features and to dramatically increase speed.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
28if (!defined('NOTOKENRENEWAL')) {
29 define('NOTOKENRENEWAL', 1); // Disables token renewal
30}
31if (!defined('NOREQUIREMENU')) {
32 define('NOREQUIREMENU', '1');
33}
34if (!defined('NOREQUIREHTML')) {
35 define('NOREQUIREHTML', '1');
36}
37if (!defined('NOREQUIREAJAX')) {
38 define('NOREQUIREAJAX', '1');
39}
40if (!defined('NOHEADERNOFOOTER')) {
41 define('NOHEADERNOFOOTER', '1');
42}
43
44require_once '../../main.inc.php';
52require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
53require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
54require_once DOL_DOCUMENT_ROOT.'/ai/class/ai.class.php';
55
56
57if (GETPOST('lang', 'aZ09')) {
58 $langs->setDefaultLang(GETPOST('lang', 'aZ09')); // If language was forced on URL by the main.inc.php
59}
60
61$langs->loadLangs(array("main", "other", "exports"));
62
63$action = GETPOST('action', 'aZ09');
64$modulepart = GETPOST('modulepart', 'aZ09');
65
66$upload_dir = $conf->user->dir_temp.'/import';
67
68// Delete the temporary files that are used when uploading files
69//dol_delete_file($upload_dir.'/upload_page-by'.$user->id.'-*');
70
71$file = GETPOST('file');
72
73$reg = array();
74if (preg_match('/^upload_page-([a-z_]+)-uid(\d+)-/', $file, $reg)) {
75 $modulepart = $reg[1];
76
77 if ($reg[2] != $user->id) {
78 accessforbidden('User id found in filename to process does not match current user id');
79 }
80} else {
81 accessforbidden('Bad value for file parameter');
82}
83
84$error = 0;
85$errors = array();
86
87$ai = new Ai($db);
88
89
90/*
91 * Actions
92 */
93
94//
95
96
97
98/*
99 * View
100 */
101
102top_httphead('application/json');
103
104$originalfilename = $file;
105$uid = $thiid = $pid = $erid = $salid = 0;
106if (preg_match('/-uid([\d+])/', $file, $reg)) {
107 $uid = $reg[1];
108 $originalfilename = preg_replace('/-uid\d+/', '', $originalfilename);
109}
110if (preg_match('/-thiid([\d+])/', $file, $reg)) {
111 $thiid = $reg[1];
112 $originalfilename = preg_replace('/-thiid\d+/', '', $originalfilename);
113}
114if (preg_match('/-pid([\d+])/', $file, $reg)) {
115 $pid = $reg[1];
116 $originalfilename = preg_replace('/-pid\d+/', '', $originalfilename);
117}
118if (preg_match('/-erid([\d+])/', $file, $reg)) {
119 $erid = $reg[1];
120 $originalfilename = preg_replace('/-erid\d+/', '', $originalfilename);
121}
122if (preg_match('/-salid([\d+])/', $file, $reg)) {
123 $salid = $reg[1];
124 $originalfilename = preg_replace('/-salid\d+/', '', $originalfilename);
125}
126$originalfilename = preg_replace('/^upload_page-[a-z_]+-/', '', $originalfilename);
127
128
129
130
131//$METHOD = 'thread'; // For ChatGPT.
132$METHOD = 'converttotext'; // For Mistral and most others
133
134
135$docformat = $doctypelabel = $prompt = '';
136$fullpathoffile = $upload_dir.'/'.$file;
137$answer = null;
138$fileContent = '';
139
140// Set the prompting
141if ($modulepart == 'invoice_supplier') {
142 $docformat = 'pdf';
143 $doctypelabel = 'invoice';
144
145 // Call IA to get information on PDF
146 $prompt = 'Analyze the contents of this '.$docformat.' document of an '.$doctypelabel.' and convert all readable text and numerical information into a structured JSON format. Follow these guidelines:
147
148 1. **Hierarchical Structure:** Group related information into logical categories like "document_info", "vendor or issuer", "recipient", "items", "payment_info" and "other" depending on the content.
149 2. **Flexible Field Identification:** Identify and organize common fields that are found such as:
150 - "invoice number", "invoice reference"
151 - "date", "issue date", "due date", "transaction or invoice date"
152 - "vendor name", "vendor vat number", "vendor professional id (siret, siren, ...), "vendor address", "vendor phone", "vendor email",
153 - "items", "products", "services", "product or service label", "product or service ref", "product or service ref"
154 - "totals", "summary", "amounts", "balance"
155 - "notes," "comments," "messages," "terms"
156 3. **Handle Tables and Lists:** If the document contains tables, represent each row as an object in a list with fields like "no", "ref", "description", "quantity", "vat rate", "unit price", "total excluding tax", "total including tax".
157 4. **Normalize Dates:** If dates are present, format them in ISO format (YYYY-MM-DD) whenever possible.
158 5. **Ignore Background Noise:** Exclude background noise, decorative elements, and irrelevant symbols that do not contribute to the data content.
159 6. **Preserve Context:** If the image contains sections, headings, or grouping indicators, use them to create logical hierarchies in the JSON structure.
160 7. **General Usability:** Format the text to be suitable for further processing, analysis, or database import.
161
162 **Example JSON Structure:**
163
164 {
165 "document_info": {
166 "document_ref": "<document ref or number>",
167 "date": "<date>",
168 "title": "<title>",
169 "currency_code": "<currency_code>"
170 },
171 "vendor": {
172 "name": "<name>",
173 "address": "<address>",
174 "phone": "<phone number>",
175 "email": "<email>",
176 "vatnumber": "<vat number>",
177 "profid": "<professional id>"
178 },
179 "recipient": {
180 "name": "<name>",
181 "address": "<address>",
182 "phone": "<phone number>",
183 "email": "<email>",
184 "vatnumber": "<vat number>",
185 "profid": "<professional id>"
186 },
187 "items": [
188 {
189 "no": "<number>",
190 "ref": "<ref>",
191 "label": "<label>",
192 "description": "<description>",
193 "quantity": "<quantity>",
194 "vatrate": "<vat rate>",
195 "totalinctax": "<total including tax>"
196 "totalexcltax": "<total excluding tax>"
197 }
198 ],
199 "summary": {
200 "subtotal": "<subtotal>",
201 "tax": "<amount of tax>",
202 "total": "<total to pay>"
203 },
204 "payment_methods": [
205 {
206 "method": "<check or cash or card or direct_debit or credit_transfer or other>",
207 "details": "Detail of the payment mode"
208 }
209 ],
210 "payments_done": [
211 {
212 "method": "<check or cash or card or direct_debit or credit_transfer or other>",
213 "amount": "<detail of the payment>",
214 "note":"<other information on payment done>"
215 }
216 ],
217 "notes": "<optional text>"
218 }
219 ';
220}
221
222
223// TODO Move this into an AJAX service and just output the JS code to call the ajax to start
224
225if ($METHOD == 'converttotext') { // @phpstan-ignore-line
226 $result = dolDocToText($fullpathoffile, '', 'fulltext');
227 if (empty($result['error'])) {
228 $fileContent = $result['content'];
229 }
230
231 if ($fileContent) {
232 $prompt = 'This is the content of the document:'."\n\n".substr($fileContent, 0, 12000)."\n\nQuestion: ".$prompt;
233
234 $result = $ai->generateContent($prompt, 'auto', 'docparsing', '');
235 // $result is an array of error messages or a string with answer
236 if (is_array($result)) { // If array, there is an error
237 if ($result['error']) {
238 $error++;
239 $errors[] = $result['error'];
240 }
241 if ($result['curl_error_no']) {
242 $error++;
243 $errors[] = $result['curl_error_no'];
244 }
245 } else {
246 $answer = (string) $result;
247
248 $answer = preg_replace('/^```json/', '', $answer);
249 $answer = preg_replace('/```$/', '', $answer);
250 }
251 } else {
252 $errors[] = 'Failed to convert document into TXT';
253 }
254}
255
256
257if ($METHOD == 'thread') { // @phpstan-ignore-line
258 $prompt = '';
259
260 $fileId = 0;
261 $assistantId = 0;
262 $threadId = 0;
263 $runId = 0;
264
265
266 // First part is to send the file
267 $payload = array(
268 "purpose" => "assistants",
269 "file" => new CURLFile($fullpathoffile)
270 );
271
272 $result = $ai->generateContent($payload, 'auto', 'file', '');
273
274 if (is_array($result)) { // If array, there is an error
275 if ($result['error']) {
276 $error++;
277 $errors[] = $result['error'];
278 }
279 if ($result['curl_error_no']) {
280 $error++;
281 $errors[] = $result['curl_error_no'];
282 }
283 } else {
284 $fileId = json_decode($result, true)['id'];
285 }
286
287
288 // Create assistant
289 if (!$error) {
290 $payload = [
291 "name" => "PDF Analyzer",
292 "instructions" => "Analyze PDF and answer precisely",
293 "tools" => [
294 ["type" => "file_search"]
295 ]
296 ];
297
298 $result = $ai->generateContent($payload, 'auto', 'assistant', '', array('OpenAI-Beta', 'assistants=v2'));
299
300 if (is_array($result)) { // If array, there is an error
301 if ($result['error']) {
302 $error++;
303 $errors[] = $result['error'];
304 }
305 if ($result['curl_error_no']) {
306 $error++;
307 $errors[] = $result['curl_error_no'];
308 }
309 } else {
310 $assistantId = json_decode($result, true)['id'];
311 }
312 }
313
314
315 // Create thread
316 if (!$error) {
317 $payload = '{}';
318
319 $result = $ai->generateContent($payload, 'auto', 'thread', '', array('OpenAI-Beta', 'assistants=v2'));
320
321 if (is_array($result)) { // If array, there is an error
322 if ($result['error']) {
323 $error++;
324 $errors[] = $result['error'];
325 }
326 if ($result['curl_error_no']) {
327 $error++;
328 $errors[] = $result['curl_error_no'];
329 }
330 } else {
331 $threadId = json_decode($result, true)['id'];
332 }
333 }
334
335
336 // Add file to thread
337 if (!$error) {
338 $payload = array(
339 "role" => "user",
340 "content" => [
341 [
342 "type" => "input_text",
343 "text" => $prompt
344 ]
345 ],
346 "attachments" => [
347 [
348 "file_id" => $fileId,
349 "tools" => [["type" => "file_search"]]
350 ]
351 ]
352 );
353 $moreendpoint = $threadId.'/messages';
354
355 $result = $ai->generateContent($payload, 'auto', 'thread', '', array('OpenAI-Beta', 'assistants=v2'), $moreendpoint);
356
357 if (is_array($result)) { // If array, there is an error
358 if ($result['error']) {
359 $error++;
360 $errors[] = $result['error'];
361 }
362 if ($result['curl_error_no']) {
363 $error++;
364 $errors[] = $result['curl_error_no'];
365 }
366 }
367 }
368
369
370 // Run the thread
371 if (!$error) {
372 $payload = array(
373 ["assistant_id" => $assistantId]
374 );
375 $moreendpoint = $threadId.'/runs';
376
377 $result = $ai->generateContent($payload, 'auto', 'thread', '', array('OpenAI-Beta', 'assistants=v2'), $moreendpoint);
378
379 if (is_array($result)) { // If array, there is an error
380 if ($result['error']) {
381 $error++;
382 $errors[] = $result['error'];
383 }
384 if ($result['curl_error_no']) {
385 $error++;
386 $errors[] = $result['curl_error_no'];
387 }
388 } else {
389 $runId = json_decode($result, true)['id'];
390 }
391 }
392
393
394 // Poll until end of thread
395 if (!$error) {
396 do {
397 sleep(1);
398
399 $payload = '';
400 $moreendpoint = $threadId.'/runs/'.$runId;
401
402 $result = $ai->generateContent($payload, 'auto', 'thread', '', array('OpenAI-Beta', 'assistants=v2'), $moreendpoint);
403
404 if (is_array($result)) { // If array, there is an error
405 if ($result['error']) {
406 $error++;
407 $errors[] = $result['error'];
408 }
409 if ($result['curl_error_no']) {
410 $error++;
411 $errors[] = $result['curl_error_no'];
412 }
413
414 $status = 'completed';
415 } else {
416 $status = json_decode($result, true)['status'];
417 }
418 } while ($status !== "completed");
419 }
420
421
422 // Get answer
423 if (!$error) {
424 $payload = '';
425 $moreendpoint = $threadId.'/messages';
426
427 $result = $ai->generateContent($prompt, 'auto', 'thread', '', array('OpenAI-Beta', 'assistants=v2'));
428
429 if (is_array($result)) { // If array, there is an error
430 if ($result['error']) {
431 $error++;
432 $errors[] = $result['error'];
433 }
434 if ($result['curl_error_no']) {
435 $error++;
436 $errors[] = $result['curl_error_no'];
437 }
438 } else {
439 $answer = $result;
440 }
441 }
442}
443
444
445// End of page
446
447$db->close();
448
449
450if (!empty($errors)) {
451 http_response_code(500);
452
453 print json_encode(array('errors' => $errors));
454} else {
455 $data = json_decode((string) $answer, true);
456
457 if ($data == null) {
458 http_response_code(500);
459
460 $error++;
461 $errors[] = 'Failed to decode answer';
462 print 'Failed to decode answer';
463 } else {
464 print $answer;
465 }
466}
Class for AI feature.
Definition ai.class.php:36
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
dolDocToText($filetoprocess, $useFullTextIndexation='pdftotext', $options='html')
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.