diff --git a/htdocs/admin/mails.php b/htdocs/admin/mails.php index d74df8ceac90a..8f797ea25e5d1 100644 --- a/htdocs/admin/mails.php +++ b/htdocs/admin/mails.php @@ -1113,7 +1113,7 @@ function change_smtp_auth_method() { print dol_get_fiche_head(array(), '', '', -1); - // Cree l'objet formulaire mail + // Create form object include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; $formmail = new FormMail($db); $formmail->trackid = (($action == 'testhtml') ? "testhtml" : "test"); @@ -1132,7 +1132,7 @@ function change_smtp_auth_method() { $formmail->withtopic = (GETPOSTISSET('subject') ? GETPOST('subject') : $langs->trans("Test")); $formmail->withtopicreadonly = 0; $formmail->withfile = 2; - $formmail->withlayout = 1; + $formmail->withlayout = 1; // Not MAIN_EMAIL_USE_LAYOUT must be set $formmail->withaiprompt = ($action == 'testhtml' ? 'html' : 'text'); $formmail->withbody = (GETPOSTISSET('message') ? GETPOST('message', 'restricthtml') : ($action == 'testhtml' ? $langs->transnoentities("PredefinedMailTestHtml") : $langs->transnoentities("PredefinedMailTest"))); $formmail->withbodyreadonly = 0; diff --git a/htdocs/ai/admin/custom_prompt.php b/htdocs/ai/admin/custom_prompt.php index 92b42924dd654..aa8e1b86292f5 100644 --- a/htdocs/ai/admin/custom_prompt.php +++ b/htdocs/ai/admin/custom_prompt.php @@ -34,8 +34,15 @@ // Parameters $action = GETPOST('action', 'aZ09'); $backtopage = GETPOST('backtopage', 'alpha'); +$cancel = GETPOST('cancel'); $modulepart = GETPOST('modulepart', 'aZ09'); // Used by actions_setmoduleoptions.inc.php +$functioncode = GETPOST('functioncode', 'alpha'); +$pre_prompt = GETPOST('prePrompt'); +$post_prompt = GETPOST('postPrompt'); +$blacklists = GETPOST('blacklists'); +$test = GETPOST('test'); + if (empty($action)) { $action = 'edit'; } @@ -83,20 +90,16 @@ * Actions */ -$functioncode = GETPOST('functioncode', 'alpha'); -$pre_prompt = GETPOST('prePrompt'); -$post_prompt = GETPOST('postPrompt'); -$blacklists = GETPOST('blacklists'); // get all configs in const AI $currentConfigurationsJson = getDolGlobalString('AI_CONFIGURATIONS_PROMPT'); $currentConfigurations = json_decode($currentConfigurationsJson, true); -if ($action == 'update' && GETPOST('cancel')) { +if ($action == 'update' && $cancel) { $action = 'edit'; } -if ($action == 'update' && !GETPOST('cancel')) { +if ($action == 'update' && !$cancel && !$test) { $error = 0; if (empty($functioncode)) { $error++; @@ -135,7 +138,8 @@ $action = 'edit'; } -if ($action == 'updatePrompts') { +// Update entry +if ($action == 'updatePrompts' && !$test) { $key = GETPOST('key', 'alpha'); $blacklistArray = array_filter(array_map('trim', explode(',', $blacklists))); @@ -149,17 +153,21 @@ $newConfigurationsJson = json_encode($currentConfigurations, JSON_UNESCAPED_UNICODE); $result = dolibarr_set_const($db, 'AI_CONFIGURATIONS_PROMPT', $newConfigurationsJson, 'chaine', 0, '', $conf->entity); if (!$error) { - $action = ''; + $action = 'edit'; if ($result) { - header("Location: ".$_SERVER['PHP_SELF']); setEventMessages($langs->trans("SetupSaved"), null, 'mesgs'); - exit; } else { setEventMessages($langs->trans("ErrorUpdating"), null, 'errors'); } } } +// Test entry +if ($action == 'updatePrompts' && $test) { + $action = 'edit'; +} + +// Delete entry if ($action == 'confirm_deleteproperty' && GETPOST('confirm') == 'yes') { $key = GETPOST('key', 'alpha'); @@ -311,12 +319,12 @@ $out .= ''; $out .= ''; $out .= ''; + $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; @@ -329,7 +337,7 @@ $out .= ''.$langs->trans("Pre-Prompt").''; $out .= ''; $out .= ''; $out .= ''; @@ -338,21 +346,34 @@ $out .= ''.$langs->trans("Post-Prompt").''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; $out .= ''; @@ -363,51 +384,6 @@ } } - - $out .= ""; - print $out; print '
'; diff --git a/htdocs/ai/admin/setup.php b/htdocs/ai/admin/setup.php index 8cd4e7e5bbb2b..b2dc936407392 100644 --- a/htdocs/ai/admin/setup.php +++ b/htdocs/ai/admin/setup.php @@ -81,11 +81,13 @@ $item->nameText = $langs->trans("AI_API_KEY").' ('.$ialabel.')'; $item->defaultFieldValue = ''; $item->fieldParams['hideGenerateButton'] = 1; + $item->fieldParams['trClass'] = $ia; $item->cssClass = 'minwidth500 text-security'; $item = $formSetup->newItem('AI_API_'.strtoupper($ia).'_URL'); // Name of constant must end with _KEY so it is encrypted when saved into database. $item->nameText = $langs->trans("AI_API_URL").' ('.$ialabel.')'; $item->defaultFieldValue = ''; + $item->fieldParams['trClass'] = $ia; $item->cssClass = 'minwidth500'; } diff --git a/htdocs/ai/ajax/generate_content.php b/htdocs/ai/ajax/generate_content.php index 4ee6a4c170cff..251f6689bdfa2 100644 --- a/htdocs/ai/ajax/generate_content.php +++ b/htdocs/ai/ajax/generate_content.php @@ -46,6 +46,10 @@ require_once DOL_DOCUMENT_ROOT.'/ai/class/ai.class.php'; +if (!isModEnabled('ai')) { + accessforbidden('Module AI not enabled'); +} + /* * View @@ -63,7 +67,7 @@ $ai = new Ai($db); // Get parameters -$function = empty($jsonData['function']) ? 'textgeneration' : $jsonData['function']; // Default value. Can also be 'textgenerationemail', 'textgenerationwebpage', ... +$function = empty($jsonData['function']) ? 'textgeneration' : $jsonData['function']; // Default value. Can also be 'textgeneration', 'textgenerationemail', 'textgenerationwebpage', 'imagegeneration', 'videogeneration', ... $instructions = dol_string_nohtmltag($jsonData['instructions'], 1, 'UTF-8'); $format = empty($jsonData['format']) ? '' : $jsonData['format']; @@ -80,5 +84,16 @@ print "Error returned by API call: " . $generatedContent['message']; } } else { - print $generatedContent; + if ($function == 'textgenerationemail' || $function == 'textgenerationwebpage') { + print dolPrintHTML($generatedContent); // Note that common HTML tags are NOT escaped (but a sanitization is done) + } elseif ($function == 'imagegeneration') { + // TODO + } elseif ($function == 'videogeneration') { + // TODO + } elseif ($function == 'audiogeneration') { + // TODO + } else { + // Default case 'textgeneration' + print dolPrintText($generatedContent); // Note that common HTML tags are NOT escaped (but a sanitization is done) + } } diff --git a/htdocs/ai/class/ai.class.php b/htdocs/ai/class/ai.class.php index dc496174b561b..2e45d37914be9 100644 --- a/htdocs/ai/class/ai.class.php +++ b/htdocs/ai/class/ai.class.php @@ -71,11 +71,11 @@ public function __construct($db) /** * Generate response of instructions * - * @param string $instructions Instruction to generate content - * @param string $model Model name ('gpt-3.5-turbo', 'gpt-4-turbo', 'dall-e-3', ...) - * @param string $function Code of the feature we want to use ('textgeneration', 'transcription', 'audiogeneration', 'imagegeneration', 'translation') - * @param string $format Format for output ('', 'html', ...) - * @return mixed $response + * @param string $instructions Instruction to generate content + * @param string $model Model name ('gpt-3.5-turbo', 'gpt-4-turbo', 'dall-e-3', ...) + * @param string $function Code of the feature we want to use ('textgeneration', 'transcription', 'audiogeneration', 'imagegeneration', 'translation') + * @param string $format Format for output ('', 'html', ...) + * @return string|array $response Text or array if error */ public function generateContent($instructions, $model = 'auto', $function = 'textgeneration', $format = '') { @@ -83,96 +83,100 @@ public function generateContent($instructions, $model = 'auto', $function = 'tex return array('error' => true, 'message' => 'API key is not defined for the AI enabled service ('.$this->apiService.')'); } + // $this->apiEndpoint is set here only if forced. + // In most cases, it is empty and we must get it from $function and $this->apiService if (empty($this->apiEndpoint)) { if ($function == 'imagegeneration') { if ($this->apiService == 'chatgpt') { $this->apiEndpoint = getDolGlobalString('AI_API_CHATGPT_URL', 'https://api.openai.com/v1').'/images/generations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CHATGPT_MODEL_IMAGE', 'dall-e-3'); - } } elseif ($this->apiService == 'groq') { $this->apiEndpoint = getDolGlobalString('AI_API_GROK_URL', 'https://api.groq.com/openai/v1').'/images/generations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_GROK_MODEL_IMAGE', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' - } } elseif ($this->apiService == 'custom') { $this->apiEndpoint = getDolGlobalString('AI_API_CUSTOM_URL', '').'/images/generations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CUSTOM_MODEL_IMAGE', 'dall-e-3'); - } } } elseif ($function == 'audiogeneration') { if ($this->apiService == 'chatgpt') { $this->apiEndpoint = getDolGlobalString('AI_API_CHATGPT_URL', 'https://api.openai.com/v1').'/audio/speech'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CHATGPT_MODEL_AUDIO', 'tts-1'); - } } elseif ($this->apiService == 'groq') { $this->apiEndpoint = getDolGlobalString('AI_API_GROK_URL', 'https://api.groq.com/openai/v1').'/audio/speech'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_GROK_MODEL_AUDIO', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' - } } elseif ($this->apiService == 'custom') { $this->apiEndpoint = getDolGlobalString('AI_API_CUSTOM_URL', '').'/audio/speech'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CUSTOM_MODEL_AUDIO', 'tts-1'); - } } } elseif ($function == 'transcription') { if ($this->apiService == 'chatgpt') { $this->apiEndpoint = getDolGlobalString('AI_API_CHATGPT_URL', 'https://api.openai.com/v1').'/transcriptions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSCRIPT', 'whisper-1'); - } } elseif ($this->apiService == 'groq') { $this->apiEndpoint = getDolGlobalString('AI_API_GROK_URL', 'https://api.groq.com/openai/v1').'/transcriptions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_GROK_MODEL_TRANSCRIPT', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' - } } elseif ($this->apiService == 'custom') { $this->apiEndpoint = getDolGlobalString('AI_API_CUSTOM_URL', '').'/transcriptions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CUSTOM_TRANSCRIPT', 'whisper-1'); - } } } elseif ($function == 'translation') { if ($this->apiService == 'chatgpt') { $this->apiEndpoint = getDolGlobalString('AI_API_CHATGPT_URL', 'https://api.openai.com/v1').'/translations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSLATE', 'whisper-1'); - } } elseif ($this->apiService == 'groq') { $this->apiEndpoint = getDolGlobalString('AI_API_GROK_URL', 'https://api.groq.com/openai/v1').'/translations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_GROK_MODEL_TRANSLATE', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' - } } elseif ($this->apiService == 'custom') { $this->apiEndpoint = getDolGlobalString('AI_API_CUSTOM_URL', '').'/translations'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CUSTOM_TRANSLATE', 'whisper-1'); - } } } else { // else textgeneration... if ($this->apiService == 'chatgpt') { $this->apiEndpoint = getDolGlobalString('AI_API_CHATGPT_URL', 'https://api.openai.com/v1').'/chat/completions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TEXT', 'gpt-3.5-turbo'); - } } elseif ($this->apiService == 'groq') { $this->apiEndpoint = getDolGlobalString('AI_API_GROK_URL', 'https://api.groq.com/openai/v1').'/chat/completions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_GROK_MODEL_TEXT', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' - } } elseif ($this->apiService == 'custom') { $this->apiEndpoint = getDolGlobalString('AI_API_CUSTOM_URL', '').'/chat/completions'; - if ($model == 'auto') { - $model = getDolGlobalString('AI_API_CUSTOM_MODEL_TEXT', 'gpt-3.5-turbo'); - } } } } - dol_syslog("Call API for apiEndpoint=".$this->apiEndpoint." apiKey=".substr($this->apiKey, 0, 3).'***********, model='.$model); + // $model may be undefined or 'auto'. + // If this is the case, we must get it from $function and $this->apiService + if (empty($model) || $model == 'auto') { + // Return the endpoint and the model from $this->apiService. + if ($function == 'imagegeneration') { + if ($this->apiService == 'chatgpt') { + $model = getDolGlobalString('AI_API_CHATGPT_MODEL_IMAGE', 'dall-e-3'); + } elseif ($this->apiService == 'groq') { + $model = getDolGlobalString('AI_API_GROK_MODEL_IMAGE', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' + } elseif ($this->apiService == 'custom') { + $model = getDolGlobalString('AI_API_CUSTOM_MODEL_IMAGE', 'dall-e-3'); + } + } elseif ($function == 'audiogeneration') { + if ($this->apiService == 'chatgpt') { + $model = getDolGlobalString('AI_API_CHATGPT_MODEL_AUDIO', 'tts-1'); + } elseif ($this->apiService == 'groq') { + $model = getDolGlobalString('AI_API_GROK_MODEL_AUDIO', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' + } elseif ($this->apiService == 'custom') { + $model = getDolGlobalString('AI_API_CUSTOM_MODEL_AUDIO', 'tts-1'); + } + } elseif ($function == 'transcription') { + if ($this->apiService == 'chatgpt') { + $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSCRIPT', 'whisper-1'); + } elseif ($this->apiService == 'groq') { + $model = getDolGlobalString('AI_API_GROK_MODEL_TRANSCRIPT', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' + } elseif ($this->apiService == 'custom') { + $model = getDolGlobalString('AI_API_CUSTOM_TRANSCRIPT', 'whisper-1'); + } + } elseif ($function == 'translation') { + if ($this->apiService == 'chatgpt') { + $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TRANSLATE', 'whisper-1'); + } elseif ($this->apiService == 'groq') { + $model = getDolGlobalString('AI_API_GROK_MODEL_TRANSLATE', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' + } elseif ($this->apiService == 'custom') { + $model = getDolGlobalString('AI_API_CUSTOM_TRANSLATE', 'whisper-1'); + } + } else { // else textgeneration... + if ($this->apiService == 'chatgpt') { + $model = getDolGlobalString('AI_API_CHATGPT_MODEL_TEXT', 'gpt-3.5-turbo'); + } elseif ($this->apiService == 'groq') { + $model = getDolGlobalString('AI_API_GROK_MODEL_TEXT', 'mixtral-8x7b-32768'); // 'llama3-8b-8192', 'gemma-7b-it' + } elseif ($this->apiService == 'custom') { + $model = getDolGlobalString('AI_API_CUSTOM_MODEL_TEXT', 'tinyllama-1.1b'); // with JAN: 'tinyllama-1.1b', 'mistral-ins-7b-q4' + } + } + } + + dol_syslog("Call API for apiKey=".substr($this->apiKey, 0, 3).'***********, apiEndpoint='.$this->apiEndpoint.", model=".$model); try { if (empty($this->apiEndpoint)) { @@ -187,42 +191,72 @@ public function generateContent($instructions, $model = 'auto', $function = 'tex if (isset($configurations[$function])) { if (isset($configurations[$function]['prePrompt'])) { - $prePrompt = $configurations[$function]['prePrompt']; + $prePrompt = $configurations[$function]['prePrompt']; // TODO We can send prePrompt into a separated message with role system. } if (isset($configurations[$function]['postPrompt'])) { $postPrompt = $configurations[$function]['postPrompt']; } } - $fullInstructions = $prePrompt.' '.$instructions.' .'.$postPrompt; - + $fullInstructions = ($prePrompt ? $prePrompt.' ' : '').$instructions.($postPrompt ? '. '.$postPrompt : ''); + // Set payload string + /*{ + "messages": [ + { + "content": "You are a helpful assistant.", + "role": "system" + }, + { + "content": "Hello!", + "role": "user" + } + ], + "model": "tinyllama-1.1b", + "stream": true, + "max_tokens": 2048, + "stop": [ + "hello" + ], + "frequency_penalty": 0, + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 + }*/ $payload = json_encode([ 'messages' => [ ['role' => 'user', 'content' => $fullInstructions] ], - 'model' => $model + 'model' => $model, + //'stream' => false ]); $headers = ([ 'Authorization: Bearer ' . $this->apiKey, 'Content-Type: application/json' ]); - $response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers); + + $localurl = 2; // Accept both local and external endpoints + $response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers, array('http', 'https'), $localurl); if (empty($response['http_code'])) { throw new Exception('API request failed. No http received'); } if (!empty($response['http_code']) && $response['http_code'] != 200) { - throw new Exception('API request on endpoint '.$this->apiEndpoint.' failed with status code ' . $response['http_code']); + throw new Exception('API request on AI endpoint '.$this->apiEndpoint.' failed with status code '.$response['http_code'].(empty($response['content']) ? '' : ' - '.$response['content'])); + } + + if (getDolGlobalString("AI_DEBUG")) { + dol_syslog("response content = ".var_export($response['content'], true)); } + // Decode JSON response $decodedResponse = json_decode($response['content'], true); // Extraction content $generatedContent = $decodedResponse['choices'][0]['message']['content']; - dol_syslog("generatedContent=".$generatedContent); + dol_syslog("generatedContent=".dol_trunc($generatedContent, 50)); // If content is not HTML, we convert it into HTML if ($format == 'html') { diff --git a/htdocs/compta/prelevement/class/bonprelevement.class.php b/htdocs/compta/prelevement/class/bonprelevement.class.php index 9f60156c6a81e..e8770f12f0ea0 100644 --- a/htdocs/compta/prelevement/class/bonprelevement.class.php +++ b/htdocs/compta/prelevement/class/bonprelevement.class.php @@ -2211,13 +2211,13 @@ public function EnregDestinataireSEPA($row_code_client, $row_nom, $row_address, $XML_DEBITOR .= ' false' . $CrLf; $XML_DEBITOR .= ' ' . $CrLf; $XML_DEBITOR .= ' ' . $CrLf; + $XML_DEBITOR .= ' ' . $CrLf; + $XML_DEBITOR .= ' ' . $CrLf; if (getDolGlobalInt('WITHDRAWAL_WITHOUT_BIC')==0) { - $XML_DEBITOR .= ' ' . $CrLf; - $XML_DEBITOR .= ' ' . $CrLf; $XML_DEBITOR .= ' ' . $row_bic . '' . $CrLf; - $XML_DEBITOR .= ' ' . $CrLf; - $XML_DEBITOR .= ' ' . $CrLf; } + $XML_DEBITOR .= ' ' . $CrLf; + $XML_DEBITOR .= ' ' . $CrLf; $XML_DEBITOR .= ' ' . $CrLf; $XML_DEBITOR .= ' ' . dolEscapeXML(strtoupper(dol_string_nospecial(dol_string_unaccent($row_nom), ' '))) . '' . $CrLf; $XML_DEBITOR .= ' ' . $CrLf; diff --git a/htdocs/core/boxes/box_factures_imp.php b/htdocs/core/boxes/box_factures_imp.php index 4c7473046d551..f5ac81853497a 100644 --- a/htdocs/core/boxes/box_factures_imp.php +++ b/htdocs/core/boxes/box_factures_imp.php @@ -139,10 +139,9 @@ public function loadBox($max = 5) while ($line < min($num, $this->max)) { $objp = $this->db->fetch_object($result); - $datelimite = $this->db->jdate($objp->datelimite); $date = $this->db->jdate($objp->date); $datem = $this->db->jdate($objp->tms); - $datelimit = $this->db->jdate(datelimite); + $datelimit = $this->db->jdate($obj->datelimite); $facturestatic->id = $objp->facid; $facturestatic->ref = $objp->ref; @@ -182,7 +181,7 @@ public function loadBox($max = 5) $late = ''; if ($facturestatic->hasDelay()) { // @phan-suppress-next-line PhanPluginPrintfVariableFormatString - $late = img_warning(sprintf($l_due_date, dol_print_date($datelimite, 'day', 'tzuserrel'))); + $late = img_warning(sprintf($l_due_date, dol_print_date($datelimit, 'day', 'tzuserrel'))); } $this->info_box_contents[$line][] = array( @@ -204,8 +203,8 @@ public function loadBox($max = 5) ); $this->info_box_contents[$line][] = array( - 'td' => 'class="center nowraponall" title="'.dol_escape_htmltag($langs->trans("DateDue").': '.dol_print_date($datelimite, 'day', 'tzuserrel')).'"', - 'text' => dol_print_date($datelimite, 'day', 'tzuserrel'), + 'td' => 'class="center nowraponall" title="'.dol_escape_htmltag($langs->trans("DateDue").': '.dol_print_date($datelimit, 'day', 'tzuserrel')).'"', + 'text' => dol_print_date($datelimit, 'day', 'tzuserrel'), ); $this->info_box_contents[$line][] = array( diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index 84a7a948fa4f9..0b8f66126f5be 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -63,17 +63,17 @@ class FormMail extends Form public $frommail; /** - * @var string user, company, robot + * @var string user, company, robot */ public $fromtype; /** - * @var int from ID + * @var int from ID */ public $fromid; /** - * @var int also from robot + * @var int Add also the robot email as possible senders */ public $fromalsorobot; @@ -93,7 +93,7 @@ class FormMail extends Form public $replytoname; /** - * @var string replyto email + * @var string Reply-to email */ public $replytomail; @@ -1419,42 +1419,38 @@ public function getSectionForAIPrompt($function = 'textgeneration', $format = '' $htmlContent = preg_replace('/[^a-z0-9_]/', '', $htmlContent); - $out = ''; - $out .= ''; - - $out .= '\n"; + + $out .= "\n"; $out .= "
'.$arrayofaifeatures[$key]['picto'].' '.$langs->trans($arrayofaifeatures[$key]['label']); - $out .= ''.img_edit().''; $out .= ''.img_delete().''; $out .= ''; - $out .= ''; + $out .= ''; $out .= '
'; - $out .= ''; + $out .= ''; $out .= '
'.$langs->trans("BlackListWords").''; - $out .= ''; + $out .= ''; $out .= '
'; - $out .= ''; + $out .= ''; + $out .= '   '; + + include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php'; + $showlinktoai = $key; // 'textgeneration', 'imagegeneration', ... + $showlinktoailabel = $langs->trans("ToTest"); + $formmail = new FormMail($db); + $htmlname = $key; + + // Fill $out + include DOL_DOCUMENT_ROOT.'/core/tpl/formlayoutai.tpl.php'; + + $out .= '
'; + $out .= '