dolibarr 23.0.3
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 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 ? '<br>' : '');
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 ? '<br>' : '');
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, 'minwidth250 ai_summarize'.$htmlContent.'_select', 1);
143 $out .= '</div>';
144 }
145
146 if (empty($onlyenhancements) || in_array($onlyenhancements, array('textrephrase'))) {
147 $stylearray = getListForAIRephraseStyle();
148 $out .= ($out ? '<br>' : '');
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, 'minwidth250 ai_rephraser'.$htmlContent.'_select', 1);
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 lang '+$(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 lang '+$(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 texttomodify = '';
243
244 console.log('htmlname='+htmlname+' functionai='+functionai);
245 if ($('#'+htmlname).is('div')) {
246 texttomodify = $('#'+htmlname).html(); /* for div */
247 } else {
248 texttomodify = $('#'+htmlname).val(); /* for input or textarea */
249 }
250 if (functionai == 'texttranslation') {
251 /*
252 if (CKEDITOR.instances) {
253 editorInstance = CKEDITOR.instances[htmlname];
254 if (editorInstance) {
255 texttomodify = editorInstance.getData();
256 }
257 }
258 */
259 if (!texttomodify) {
260 instructions = '';
261 } else {
262 lang = $('#ai_translation'+htmlname+'_select').val();
263 instructions = 'Translate only the following text to ' + lang + ': ' + texttomodify;
264 }
265 } else if (functionai == 'textsummarize') {
266 width = $('#ai_summarize'+htmlname+'_select').val();
267 arr = width.split('_');
268 width = arr[0];
269 unit = arr[1];
270 if (width == undefined || unit == undefined){
271 console.log('Bad value so we choose 20 words')
272 width = '50';
273 unit = 'w';
274 }
275 switch(unit){
276 case 'w':
277 unit = 'words';
278 break;
279 case 'p':
280 unit = 'paragraphs';
281 break;
282 case 'pc':
283 unit = 'percent';
284 break;
285 default:
286 console.log('unit not found so we choose words');
287 unit = 'words';
288 break;
289 }
290 instructions = 'Summarize the following text '+ (unit == 'percent' ? 'by ' : 'in') + width + ' ' + unit + ': ' + texttomodify;
291 } else if (functionai == 'textrephraser') {
292 style = $('#ai_rephraser'+htmlname+'_select').val();
293 instructions = 'Rephrase the following text in a '+style+' style: ' + texttomodify;
294 } else if (functionai == 'textgenerationextrafield'){
295 instructions = $(element).val();
296 } else {
297 instructions = userprompt;
298 }
299
300 /* Show message API running */
301 $('#ai_status_message".$htmlContent."').show();
302 $('#ai_status_message".$htmlContent."').html('".dol_escape_js($messageaiwait)."');
303 $('.icon-container .loader').show();
304
305 setTimeout(function() {
306 timeoutfinished = 1;
307 $('#ai_status_message".$htmlContent."').hide();
308 }, 30000);
309
310 console.log('Instruction forged by javascript = '+instructions);
311
312 callAIGenerator(functionai, instructions, format, htmlname);
313 }
314
315 CKEDITOR.on( 'instanceReady', function(e) {
316 if (CKEDITOR.instances) {
317 var htmlname = '".$htmlContent."';
318 /* if a ckeditor handler exist for this div, we add a handler to have the main html component updated */
319 console.log('Add handler on CKEDITOR.instances[".$htmlContent."]');
320 if (CKEDITOR.instances[htmlname] != undefined) {
321 CKEDITOR.instances[htmlname].on('change', function() {
322 data = CKEDITOR.instances[htmlname].getData();
323 $('#'+htmlname).val(data); /* for input or textarea */
324 $('#'+htmlname).html(data); /* for div */
325 })
326 }
327 }
328 })
329 });
330 </script>
331 ";
332 return $out;
333 }
334
340 public function getAjaxAICallFunction()
341 {
342 $out = "";
343 if ($this->aicallfunctioncalled) {
344 return $out;
345 }
346
347 $out .= "
348 <script>
349 function callAIGenerator(aifunction, instructions, format, htmlname){
350 if (aifunction === 'imagegeneration') {
351 // Handle image generation request
352 $.ajax({
353 url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."',
354 type: 'POST',
355 contentType: 'application/json',
356 data: JSON.stringify({
357 'format': format, /* the format for output */
358 'function': aifunction, /* the AI feature to call */
359 'instructions': instructions, /* the prompt string */
360 }),
361 success: function(response) {
362 console.log('Received image URL: '+response);
363
364 // make substitutions
365 let substit = ". json_encode($this->substit).";
366 for (let key in substit) {
367 if (substit.hasOwnProperty(key)) {
368 // Replace the placeholder with its corresponding value
369 response = response.replace(key, substit[key]);
370 }
371 }
372
373 // Assuming response is the URL of the generated image
374 var imageUrl = response;
375 $('#ai_image_result').html('<img src=\"' + imageUrl + '\" alt=\"Generated Image\" />');
376
377 // Clear the input field
378 $('#ai_instructions').val('');
379
380 apicallfinished = 1;
381 if (timeoutfinished) {
382 $('#ai_status_message').hide();
383 }
384 },
385 error: function(xhr, status, error) {
386 /* alert(error); */
387 console.log('error ajax', status, error);
388 /*$('#ai_status_message'+htmlname).hide();*/
389 $('#ai_status_message'+htmlname).val(error);
390 $('#ai_status_message'+htmlname).html(error);
391 }
392 });
393 } else {
394
395 // set editor in readonly
396 if (CKEDITOR.instances[htmlname]) {
397 CKEDITOR.instances[htmlname].setReadOnly(1);
398 }
399
400 console.log('Call generate_content.php');
401 $.ajax({
402 url: '". DOL_URL_ROOT."/ai/ajax/generate_content.php?token=".currentToken()."',
403 type: 'POST',
404 contentType: 'application/json',
405 data: JSON.stringify({
406 'format': format, /* the format for output */
407 'function': aifunction, /* the AI feature to call */
408 'instructions': instructions, /* the prompt string */
409 }),
410 success: function(response) {
411 console.log('Add response into field \'#'+htmlname+'\': '+response);
412
413 jQuery('#'+htmlname).val(response); // If #htmlcontent is a input name or textarea
414 jQuery('#'+htmlname).html(response).trigger('change'); // If #htmlContent is a div and trigger event change for extrafield update
415 //jQuery('#'+htmlname+'preview').val(response);
416
417 if (CKEDITOR.instances) {
418 var editorInstance = CKEDITOR.instances[htmlname];
419 if (editorInstance) {
420 editorInstance.setReadOnly(0);
421 editorInstance.setData(response);
422 }
423 //var editorInstancepreview = CKEDITOR.instances[htmlname+'preview'];
424 //if (editorInstancepreview) {
425 // editorInstancepreview.setData(response);
426 //}
427 }
428
429 // Remove all value from Ai Section select
430 $('#ai_instructions'+htmlname).val('');
431 $('#ai_translation'+htmlname+'_select').val('-1');
432 $('#ai_translation'+htmlname+'_select').trigger('change');
433 $('#ai_summarize'+htmlname+'_select').val('-1');
434 $('#ai_summarize'+htmlname+'_select').trigger('change');
435 $('#ai_rephraser'+htmlname+'_select').val('-1');
436 $('#ai_rephraser'+htmlname+'_select').trigger('change');
437 $('#ai_status_message'+htmlname).hide();
438 $('#ai_dropdown'+htmlname).hide();
439 },
440 error: function(xhr, status, error) {
441 /* alert(error); */
442 console.log('error ajax ', status, error);
443 /* $('#ai_status_message'+htmlname).hide(); */
444 if (xhr.responseText) {
445 $('#ai_status_message'+htmlname).val(xhr.responseText);
446 $('#ai_status_message'+htmlname).html(xhr.responseText);
447 } else {
448 $('#ai_status_message'+htmlname).val(error);
449 $('#ai_status_message'+htmlname).html(error);
450 }
451 }
452
453 });
454 }
455 }
456 </script>";
457 $this->aicallfunctioncalled = true;
458 return $out;
459 }
460
469 public function setSubstitFromObject($object, $outputlangs)
470 {
471 global $extrafields;
472
473 $parameters = array();
474 $tmparray = getCommonSubstitutionArray($outputlangs, 0, null, $object);
475 complete_substitutions_array($tmparray, $outputlangs, null, $parameters);
476
477 $this->substit = $tmparray;
478
479 // Fill substit_lines with each object lines content
480 if (is_array($object->lines)) {
481 foreach ($object->lines as $line) {
482 $substit_line = array(
483 '__PRODUCT_REF__' => isset($line->product_ref) ? $line->product_ref : '',
484 '__PRODUCT_LABEL__' => isset($line->product_label) ? $line->product_label : '',
485 '__PRODUCT_DESCRIPTION__' => isset($line->product_desc) ? $line->product_desc : '',
486 '__LABEL__' => isset($line->label) ? $line->label : '',
487 '__DESCRIPTION__' => isset($line->desc) ? $line->desc : '',
488 '__DATE_START_YMD__' => dol_print_date($line->date_start, 'day', false, $outputlangs),
489 '__DATE_END_YMD__' => dol_print_date($line->date_end, 'day', false, $outputlangs),
490 '__QUANTITY__' => $line->qty,
491 '__SUBPRICE__' => price($line->subprice),
492 '__AMOUNT__' => price($line->total_ttc),
493 '__AMOUNT_EXCL_TAX__' => price($line->total_ht)
494 );
495
496 // Create dynamic tags for __PRODUCT_EXTRAFIELD_FIELD__
497 if (!empty($line->fk_product)) {
498 if (!is_object($extrafields)) {
499 $extrafields = new ExtraFields($this->db);
500 }
501 $product = new Product($this->db);
502 $product->fetch($line->fk_product);
503 $product->fetch_optionals();
504
505 $extrafields->fetch_name_optionals_label($product->table_element, true);
506
507 if (!empty($extrafields->attributes[$product->table_element]['label']) && is_array($extrafields->attributes[$product->table_element]['label']) && count($extrafields->attributes[$product->table_element]['label']) > 0) {
508 foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
509 $substit_line['__PRODUCT_EXTRAFIELD_'.strtoupper($key).'__'] = isset($product->array_options['options_'.$key]) ? $product->array_options['options_'.$key] : '';
510 }
511 }
512 }
513
514 $this->substit_lines[$line->id] = $substit_line; // @phan-suppress-current-line PhanTypeMismatchProperty
515 }
516 }
517 }
518}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
getListForAIRephraseStyle()
Get list for AI style of writing.
Definition ai.lib.php:152
getListForAISummarize()
Get list for AI summarize.
Definition ai.lib.php:130
Class to manage standard extra fields.
Class permettant la generation du formulaire html d'envoi de mail unitaire Usage: $formail = new Form...
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.
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)
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.