dolibarr 24.0.0-beta
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:391
getListForAISummarize()
Get list for AI summarize.
Definition ai.lib.php:369
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.
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)
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.