dolibarr 25.0.0-alpha
html.formai.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2005-2012 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2010-2011 Juanjo Menent <jmenent@2byte.es>
5 * Copyright (C) 2015-2017 Marcos García <marcosgdf@gmail.com>
6 * Copyright (C) 2015-2017 Nicolas ZABOURI <info@inovea-conseil.com>
7 * Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2022 Charlene Benke <charlene@patas-monkey.com>
9 * Copyright (C) 2023 Anthony Berton <anthony.berton@bb2a.fr>
10 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
11 *
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
32require_once DOL_DOCUMENT_ROOT.'/core/class/html.form.class.php';
33
34
41class FormAI extends Form
42{
46 public $db;
47
51 public $withlayout;
52
56 public $withaiprompt;
57
61 public $substit = array();
62
66 public $substit_lines = array();
67
71 public $param = array();
72
76 public $withoptiononeemailperrecipient;
77
81 public $aicallfunctioncalled = false;
82
88 public function __construct($db)
89 {
90 $this->db = $db;
91 }
92
103 public function getSectionForAIEnhancement($function = 'textgeneration', $format = '', $htmlContent = 'message', $onlyenhancements = '', $aiprompt = "")
104 {
105 global $langs, $form;
106 require_once DOL_DOCUMENT_ROOT."/ai/lib/ai.lib.php";
107 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formadmin.class.php';
108 $formadmin = new FormAdmin($this->db);
109
110 if (!is_object($form)) {
111 $form = new Form($this->db);
112 }
113
114 $langs->load("other");
115
116 $messageaiwait = '<i class="fa fa-spinner fa-spin fa-2x fa-fw valignmiddle marginrightonly"></i>'.$langs->trans("AIProcessingPleaseWait", getDolGlobalString('AI_API_SERVICE', 'chatgpt'));
117
118 $htmlContent = preg_replace('/[^a-z0-9_]/', '', $htmlContent);
119
120 $out = '';
121 if (empty($onlyenhancements) || in_array($onlyenhancements, array('textgenerationemail', 'textgenerationwebpage'))) {
122 $out .= '<div id="ai_textgeneration'.$htmlContent.'" class="ai_textgeneration'.$htmlContent.' paddingtop paddingbottom ai_feature">';
123 //$out .= '<span>'.$langs->trans("FillMessageWithAIContent").'</span>';
124 $out .= '<textarea class="centpercent textarea-ai_feature" data-functionai="textgeneration" id="ai_instructions'.$htmlContent.'" name="instruction" placeholder="'.$langs->trans("EnterYourAIPromptHere").'..." /></textarea>';
125 $out .= '<input id="generate_button'.$htmlContent.'" type="button" class="button smallpaddingimp" disabled data-functionai="'.$function.'" value="'.$langs->trans('Generate').'"/>';
126 $out .= '</div>';
127 }
128
129 if (empty($onlyenhancements) || in_array($onlyenhancements, array('texttranslation'))) {
130 $out .= ($out ? '<div style="height: 10px;"></div>' : '');
131 $out .= '<div id="ai_translation'.$htmlContent.'" class="ai_translation'.$htmlContent.' paddingtop paddingbottom ai_feature">';
132 $out .= img_picto('', 'language', 'class="pictofixedwidth paddingrightonly"');
133 $out .= $formadmin->select_language("", "ai_translation".$htmlContent."_select", 0, array(), $langs->trans("TranslateByAI").'...', 0, 0, 'minwidth250 ai_translation'.$htmlContent.'_select');
134 $out .= '</div>';
135 }
136
137 if (empty($onlyenhancements) || in_array($onlyenhancements, array('textsummarize'))) {
138 $summarizearray = getListForAISummarize();
139 $out .= ($out ? '<div style="height: 10px;"></div>' : '');
140 $out .= '<div id="ai_summarize'.$htmlContent.'" class="ai_summarize'.$htmlContent.' paddingtop paddingbottom ai_feature">';
141 $out .= img_picto('', 'edit', 'class="pictofixedwidth paddingrightonly"');
142 $out .= $form->selectarray("ai_summarize".$htmlContent."_select", $summarizearray, 0, $langs->trans("SummarizeByAI").'...', 0, 0, '', 1, 0, 0, '', 'minwidth250 ai_summarize'.$htmlContent.'_select');
143 $out .= '</div>';
144 }
145
146 if (empty($onlyenhancements) || in_array($onlyenhancements, array('textrephrase'))) {
147 $stylearray = getListForAIRephraseStyle();
148 $out .= ($out ? '<div style="height: 10px;"></div>' : '');
149 $out .= '<div id="ai_rephraser'.$htmlContent.'" class="ai_rephraser'.$htmlContent.' paddingtop paddingbottom ai_feature">';
150 $out .= img_picto('', 'edit', 'class="pictofixedwidth paddingrightonly"');
151 $out .= $form->selectarray("ai_rephraser".$htmlContent."_select", $stylearray, 0, $langs->trans("RephraserByAI").'...', 0, 0, '', 1, 0, 0, '', 'minwidth250 ai_rephraser'.$htmlContent.'_select');
152 $out .= '</div>';
153 }
154
155 if (in_array($onlyenhancements, array('textgenerationextrafield'))) {
156 $out .= '<div id="ai_textgenerationextrafield'.$htmlContent.'" class="ai_textgenerationextrafield'.$htmlContent.' paddingtop paddingbottom ai_feature">';
157 $out .= '<input id="input_ai_textgenerationextrafield'.$htmlContent.'" type="hidden" class="button smallpaddingimp" data-functionai="textgenerationextrafield" value="'.$aiprompt.'"/>';
158 $out .= '</div>';
159 }
160
161 $out = '<!-- getSectionForAIEnhancement -->'.$out;
162 $out = '<div id="ai_dropdown'.$htmlContent.'" class="dropdown-menu ai_dropdown ai_dropdown'.$htmlContent.' paddingtop paddingbottom">'.$out;
163
164 $out .= '<div id="ai_status_message'.$htmlContent.'" class="fieldrequired hideobject marginrightonly margintoponly">';
165 $out .= $messageaiwait;
166 $out .= '</div>';
167
168 if ($function == 'imagegeneration') {
169 $out .= '<div id="ai_image_result" class="margintoponly"></div>'; // Div for displaying the generated image
170 }
171
172 $out .= "</div>\n";
173 $out .= "<script type='text/javascript'>
174 $(document).ready(function() {
175 $('#ai_translation".$htmlContent."_select').data('functionai', 'texttranslation')
176 $('#ai_summarize".$htmlContent."_select').data('functionai', 'textsummarize')
177 $('#ai_rephraser".$htmlContent."_select').data('functionai', 'textrephraser')
178
179 $('#ai_instructions".$htmlContent."').keyup(function(){
180 console.log('We type a key up on #ai_instructions".$htmlContent."');
181 if ($(this).val() != '') {
182 $('#generate_button".$htmlContent."').prop('disabled', false);
183 } else {
184 $('#generate_button".$htmlContent."').prop('disabled', true);
185 }
186 });
187
188 // for keydown
189 $('#ai_instructions".$htmlContent."').keydown(function(event) {
190 if (event.keyCode === 13 && $(this).val() != '') {
191 console.log('We type enter on #ai_instructions".$htmlContent."');
192 event.preventDefault();
193 $('#generate_button".$htmlContent."').click();
194 }
195 });
196
197 $('#generate_button".$htmlContent."').click(function() {
198 console.log('We click on #generate_button".$htmlContent."');
199 prepareCallAIGenerator($(this));
200 });
201
202 $('#ai_translation".$htmlContent."_select').on('change', function() {
203 console.log('We change #ai_translation".$htmlContent."_select with lang '+$(this).val());
204 if ($(this).val() != null && $(this).val() != '' && $(this).val() != '-1') {
205 prepareCallAIGenerator($(this));
206 }
207 });
208
209 $('#ai_summarize".$htmlContent."_select').on('change', function() {
210 console.log('We change #ai_summarize".$htmlContent."_select with length '+$(this).val());
211 if ($(this).val() != null && $(this).val() != '' && $(this).val() != '-1') {
212 prepareCallAIGenerator($(this));
213 }
214 });
215
216 $('#ai_rephraser".$htmlContent."_select').on('change', function() {
217 console.log('We change #ai_summarize".$htmlContent."_select with mode '+$(this).val());
218 if ($(this).val() != null && $(this).val() != '' && $(this).val() != '-1') {
219 prepareCallAIGenerator($(this));
220 }
221 });
222 $('#linkforaiprompt".$function."').on('click', function() {
223 //Get value aiprompt + prepare ai generator
224 elementforprompt = $('#input_ai_textgenerationextrafield".$htmlContent."');
225 aiprompt = elementforprompt.val();
226 if (aiprompt != null && aiprompt != '' && aiprompt != '-1'){
227 prepareCallAIGenerator(elementforprompt);
228 }
229 });
230
231 function prepareCallAIGenerator(element) {
232 console.log('We prepare ajax call to AI to url /ai/ajax/generate_content.php function=".dol_escape_js($function)." format=".dol_escape_js($format)."');
233
234 var userprompt = $('#ai_instructions".$htmlContent."').val();
235 var timeoutfinished = 0;
236 var apicallfinished = 0;
237
238 instructions = '';
239 htmlname = '".dol_escape_js($htmlContent)."';
240 format = '".dol_escape_js($format)."';
241 functionai = $(element).data('functionai'); /* element is the html element we have manipulated in the ai tool */
242 style = $('#ai_rephraser'+htmlname+'_select').val();
243 texttomodify = '';
244
245 console.log('htmlname='+htmlname+' functionai='+functionai+' style='+style);
246 if ($('#'+htmlname).is('div')) {
247 texttomodify = $('#'+htmlname).html(); /* for div */
248 } else {
249 texttomodify = $('#'+htmlname).val(); /* for input or textarea */
250 }
251 if (functionai == 'texttranslation') {
252 /*
253 if (CKEDITOR.instances) {
254 editorInstance = CKEDITOR.instances[htmlname];
255 if (editorInstance) {
256 texttomodify = editorInstance.getData();
257 }
258 }
259 */
260 if (!texttomodify) {
261 instructions = '';
262 } else {
263 lang = $('#ai_translation'+htmlname+'_select').val();
264 instructions = 'Translate only the following text to ' + lang + ': ' + texttomodify;
265 }
266 } else if (functionai == 'textsummarize') {
267 width = $('#ai_summarize'+htmlname+'_select').val();
268 arr = width.split('_');
269 width = arr[0];
270 unit = arr[1];
271 if (width == undefined || unit == undefined){
272 console.log('Bad value so we choose 20 words')
273 width = '50';
274 unit = 'w';
275 }
276 switch(unit){
277 case 'w':
278 unit = 'words';
279 break;
280 case 'p':
281 unit = 'paragraphs';
282 break;
283 case 'pc':
284 unit = 'percent';
285 break;
286 default:
287 console.log('unit not found so we choose words');
288 unit = 'words';
289 break;
290 }
291 instructions = 'Summarize the following text '+ (unit == 'percent' ? 'by ' : 'in') + width + ' ' + unit + ': ' + texttomodify;
292 } else if (functionai == 'textrephraser') {
293 if (style == 'spellchecker') {
294 instructions = 'Fix spelling and grammar errors in the following text : ' + texttomodify;
295 functionai = 'textspellchecker';
296 } else {
297 instructions = 'Rephrase the following text in a '+style+' style: ' + texttomodify;
298 }
299 } else if (functionai == 'textgenerationextrafield'){
300 instructions = $(element).val();
301 } else {
302 instructions = userprompt;
303 }
304
305 /* Show message API running */
306 $('#ai_status_message".$htmlContent."').show();
307 $('#ai_status_message".$htmlContent."').html('".dol_escape_js($messageaiwait)."');
308 $('.icon-container .loader').show();
309
310 setTimeout(function() {
311 timeoutfinished = 1;
312 $('#ai_status_message".$htmlContent."').hide();
313 }, 30000);
314
315 console.log('Instruction forged by javascript = '+instructions);
316
317 callAIGenerator(functionai, instructions, format, htmlname);
318 }
319
320 CKEDITOR.on( 'instanceReady', function(e) {
321 if (CKEDITOR.instances) {
322 var htmlname = '".$htmlContent."';
323 /* if a ckeditor handler exist for this div, we add a handler to have the main html component updated */
324 console.log('Add handler on CKEDITOR.instances[".$htmlContent."]');
325 if (CKEDITOR.instances[htmlname] != undefined) {
326 CKEDITOR.instances[htmlname].on('change', function() {
327 data = CKEDITOR.instances[htmlname].getData();
328 $('#'+htmlname).val(data); /* for input or textarea */
329 $('#'+htmlname).html(data); /* for div */
330 })
331 }
332 }
333 })
334 });
335 </script>
336 ";
337 return $out;
338 }
339
345 public function getAjaxAICallFunction()
346 {
347 $out = "";
348 if ($this->aicallfunctioncalled) {
349 return $out;
350 }
351
352 $out .= "
353 <script>
354 function callAIGenerator(aifunction, instructions, format, htmlname){
355 if (aifunction === 'imagegeneration') {
356 // Handle image generation request
357 $.ajax({
358 url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."',
359 type: 'POST',
360 contentType: 'application/json',
361 data: JSON.stringify({
362 'format': format, /* the format for output */
363 'function': aifunction, /* the AI feature to call */
364 'instructions': instructions, /* the prompt string */
365 }),
366 success: function(response) {
367 console.log('Received image URL: '+response);
368
369 // make substitutions
370 let substit = ". json_encode($this->substit).";
371 for (let key in substit) {
372 if (substit.hasOwnProperty(key)) {
373 // Replace the placeholder with its corresponding value
374 response = response.replace(key, substit[key]);
375 }
376 }
377
378 // Assuming response is the URL of the generated image
379 var imageUrl = response;
380 $('#ai_image_result').html('<img src=\"' + imageUrl + '\" alt=\"Generated Image\" />');
381
382 // Clear the input field
383 $('#ai_instructions').val('');
384
385 apicallfinished = 1;
386 if (timeoutfinished) {
387 $('#ai_status_message').hide();
388 }
389 },
390 error: function(xhr, status, error) {
391 /* alert(error); */
392 console.log('error ajax', status, error);
393 /*$('#ai_status_message'+htmlname).hide();*/
394 $('#ai_status_message'+htmlname).val(error);
395 $('#ai_status_message'+htmlname).html(error);
396 }
397 });
398 } else {
399
400 // set editor in readonly
401 if (CKEDITOR.instances[htmlname]) {
402 CKEDITOR.instances[htmlname].setReadOnly(1);
403 }
404
405 console.log('Call generate_content.php');
406 $.ajax({
407 url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."',
408 type: 'POST',
409 contentType: 'application/json',
410 data: JSON.stringify({
411 'format': format, /* the format for output */
412 'function': aifunction, /* the AI feature to call */
413 'instructions': instructions, /* the prompt string */
414 }),
415 success: function(response) {
416 console.log('Add response into field \'#'+htmlname+'\': '+response);
417
418 jQuery('#'+htmlname).val(response); // If #htmlcontent is a input name or textarea
419 jQuery('#'+htmlname).html(response).trigger('change'); // If #htmlContent is a div and trigger event change for extrafield update
420 //jQuery('#'+htmlname+'preview').val(response);
421
422 if (CKEDITOR.instances) {
423 var editorInstance = CKEDITOR.instances[htmlname];
424 if (editorInstance) {
425 editorInstance.setReadOnly(0);
426 editorInstance.setData(response);
427 }
428 //var editorInstancepreview = CKEDITOR.instances[htmlname+'preview'];
429 //if (editorInstancepreview) {
430 // editorInstancepreview.setData(response);
431 //}
432 }
433
434 // Remove all value from Ai Section select
435 $('#ai_instructions'+htmlname).val('');
436 $('#ai_translation'+htmlname+'_select').val('-1');
437 $('#ai_translation'+htmlname+'_select').trigger('change');
438 $('#ai_summarize'+htmlname+'_select').val('-1');
439 $('#ai_summarize'+htmlname+'_select').trigger('change');
440 $('#ai_rephraser'+htmlname+'_select').val('-1');
441 $('#ai_rephraser'+htmlname+'_select').trigger('change');
442 $('#ai_status_message'+htmlname).hide();
443 $('#ai_dropdown'+htmlname).hide();
444 },
445 error: function(xhr, status, error) {
446 /* alert(error); */
447 console.log('error ajax ', status, error);
448 /* $('#ai_status_message'+htmlname).hide(); */
449 if (xhr.responseText) {
450 $('#ai_status_message'+htmlname).val(xhr.responseText);
451 $('#ai_status_message'+htmlname).html(xhr.responseText);
452 } else {
453 $('#ai_status_message'+htmlname).val(error);
454 $('#ai_status_message'+htmlname).html(error);
455 }
456 }
457
458 });
459 }
460 }
461 </script>";
462 $this->aicallfunctioncalled = true;
463 return $out;
464 }
465
474 public function setSubstitFromObject($object, $outputlangs)
475 {
476 global $extrafields;
477
478 $parameters = array();
479 $tmparray = getCommonSubstitutionArray($outputlangs, 0, null, $object);
480 complete_substitutions_array($tmparray, $outputlangs, null, $parameters);
481
482 $this->substit = $tmparray;
483
484 // Fill substit_lines with each object lines content
485 if (is_array($object->lines)) {
486 foreach ($object->lines as $line) {
487 $substit_line = array(
488 '__PRODUCT_REF__' => isset($line->product_ref) ? $line->product_ref : '',
489 '__PRODUCT_LABEL__' => isset($line->product_label) ? $line->product_label : '',
490 '__PRODUCT_DESCRIPTION__' => isset($line->product_desc) ? $line->product_desc : '',
491 '__LABEL__' => isset($line->label) ? $line->label : '',
492 '__DESCRIPTION__' => isset($line->desc) ? $line->desc : '',
493 '__DATE_START_YMD__' => dol_print_date($line->date_start, 'day', false, $outputlangs),
494 '__DATE_END_YMD__' => dol_print_date($line->date_end, 'day', false, $outputlangs),
495 '__QUANTITY__' => $line->qty,
496 '__SUBPRICE__' => price($line->subprice),
497 '__AMOUNT__' => price($line->total_ttc),
498 '__AMOUNT_EXCL_TAX__' => price($line->total_ht)
499 );
500
501 // Create dynamic tags for __PRODUCT_EXTRAFIELD_FIELD__
502 if (!empty($line->fk_product)) {
503 if (!is_object($extrafields)) {
504 $extrafields = new ExtraFields($this->db);
505 }
506 $product = new Product($this->db);
507 $product->fetch($line->fk_product);
508 $product->fetch_optionals();
509
510 $extrafields->fetch_name_optionals_label($product->table_element, true);
511
512 if (!empty($extrafields->attributes[$product->table_element]['label']) && is_array($extrafields->attributes[$product->table_element]['label']) && count($extrafields->attributes[$product->table_element]['label']) > 0) {
513 foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
514 $substit_line['__PRODUCT_EXTRAFIELD_'.strtoupper($key).'__'] = isset($product->array_options['options_'.$key]) ? $product->array_options['options_'.$key] : '';
515 }
516 }
517 }
518
519 $this->substit_lines[$line->id] = $substit_line; // @phan-suppress-current-line PhanTypeMismatchProperty
520 }
521 }
522 }
523}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
getListForAIRephraseStyle()
Get list for AI style of writing.
Definition ai.lib.php:392
getListForAISummarize()
Get list for AI summarize.
Definition ai.lib.php:370
Class to manage standard extra fields.
Class to generate HTML forms for single email Usage: $formai = new FormAI($db) $formai->proprietes=1 ...
getSectionForAIEnhancement($function='textgeneration', $format='', $htmlContent='message', $onlyenhancements='', $aiprompt="")
Return Html code for AI instructions of message and autofill result.
getAjaxAICallFunction()
Return javascript code for call to AI function callAIGenerator()
setSubstitFromObject($object, $outputlangs)
Set ->substit (and ->substit_line) array from object.
__construct($db)
Constructor.
Class to generate html code for admin pages.
Class to manage generation of HTML components Only common components must be here.
Class to manage products or services.
currentToken()
Return the value of token currently saved into session with name 'token'.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)