NEW Add a test mode into the setup of AI prompts

This commit is contained in:
Laurent Destailleur 2024-07-19 15:38:52 +02:00
parent c4f29dca59
commit 3a9931c0bb
7 changed files with 200 additions and 160 deletions

View File

@ -1113,7 +1113,7 @@ if ($action == 'edit') {
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 @@ if ($action == 'edit') {
$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;

View File

@ -34,8 +34,15 @@ $langs->loadLangs(array("admin", "website", "other"));
// 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 @@ $arrayofaifeatures = array(
* 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 @@ if ($action == 'update' && !GETPOST('cancel')) {
$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 @@ if ($action == 'updatePrompts') {
$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 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
$out .= '<input type="hidden" name="token" value="'.newToken().'">';
$out .= '<input type="hidden" name="key" value="'.$key.'" />';
$out .= '<input type="hidden" name="action" value="updatePrompts">';
$out .= '<input type="hidden" name="page_y" value="">';
$out .= '<table class="noborder centpercent">';
$out .= '<thead>';
$out .= '<tr class="liste_titre">';
$out .= '<td class="titlefield">'.$arrayofaifeatures[$key]['picto'].' '.$langs->trans($arrayofaifeatures[$key]['label']);
$out .= '<a class="viewfielda reposition marginleftonly marginrighttonly showInputBtn" href="#" data-index="'.$key.'" data-state="edit" data-icon-edit="'.dol_escape_htmltag(img_edit()).'" data-icon-cancel="'.dol_escape_htmltag(img_view()).'">'.img_edit().'</a>';
$out .= '<a class="deletefielda reposition marginleftonly right" href="'.$_SERVER["PHP_SELF"].'?action=deleteproperty&token='.newToken().'&key='.urlencode($key).'">'.img_delete().'</a>';
$out .= '</td>';
$out .= '<td></td>';
@ -329,7 +337,7 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
$out .= '<span id="prePrompt" class="spanforparamtooltip">'.$langs->trans("Pre-Prompt").'</span>';
$out .= '</td>';
$out .= '<td>';
$out .= '<textarea class="flat minwidth500" id="prePromptInput_'.$key.'" name="prePrompt" rows="2" disabled>'.$config['prePrompt'].'</textarea>';
$out .= '<textarea class="flat minwidth500 quatrevingtpercent" id="prePromptInput_'.$key.'" name="prePrompt" rows="2">'.$config['prePrompt'].'</textarea>';
$out .= '</td>';
$out .= '</tr>';
@ -338,21 +346,34 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
$out .= '<span id="postPrompt" class="spanforparamtooltip">'.$langs->trans("Post-Prompt").'</span>';
$out .= '</td>';
$out .= '<td>';
$out .= '<textarea class="flat minwidth500" id="postPromptInput_'.$key.'" name="postPrompt" rows="2" disabled>'.$config['postPrompt'].'</textarea>';
$out .= '<textarea class="flat minwidth500 quatrevingtpercent" id="postPromptInput_'.$key.'" name="postPrompt" rows="2">'.$config['postPrompt'].'</textarea>';
$out .= '</td>';
$out .= '</tr>';
$out .= '<tr id="fichetwothirdright-'.$key.'" class="oddeven">';
$out .= '<td>'.$langs->trans("BlackListWords").'</td>';
$out .= '<td>';
$out .= '<textarea class="flat minwidth500" id="blacklist_'.$key.'" name="blacklists" rows="6" disabled>'.(isset($config['blacklists']) ? implode(', ', (array) $config['blacklists']) : '').'</textarea>';
$out .= '<textarea class="flat minwidth500 quatrevingtpercent" id="blacklist_'.$key.'" name="blacklists" rows="3">'.(isset($config['blacklists']) ? implode(', ', (array) $config['blacklists']) : '').'</textarea>';
$out .= '</td>';
$out .= '</tr>';
$out .= '<tr>';
$out .= '<td></td>';
$out .= '<td>';
$out .= '<input type="submit" class="button small submitBtn" name="modify" data-index="'.$key.'" style="display: none;" value="'.dol_escape_htmltag($langs->trans("Modify")).'"/>';
$out .= '<input type="submit" class="button small submitBtn reposition" name="modify" data-index="'.$key.'" value="'.dol_escape_htmltag($langs->trans("Modify")).'"/>';
$out .= ' &nbsp; ';
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 .= '<div id="'.$htmlname.'"></div>';
$out .= '</td>';
$out .= '</tr>';
@ -363,51 +384,6 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
}
}
$out .= "<script>
var configurations = ".$currentConfigurationsJson.";
$(document).ready(function() {
$('#module_select').change(function() {
var selectedModule = $(this).val();
var moduleConfig = configurations[selectedModule];
if (moduleConfig) {
$('#prePromptInput').val(moduleConfig.prePrompt || '');
$('#postPromptInput').val(moduleConfig.postPrompt || '');
$('#blacklistsInput').val(moduleConfig.blacklists ? moduleConfig.blacklists.join(', ') : '');
} else {
$('#prePromptInput').val('');
$('#postPromptInput').val('');
$('#blacklistsInput').val('');
}
});
$('.showInputBtn').click(function() {
event.preventDefault();
var index = $(this).data('index');
var state = $(this).data('state');
if(state === 'edit') {
$('#prePromptInput_'+index).removeAttr('disabled').focus();
$('#postPromptInput_'+index).removeAttr('disabled');
$('#blacklist_'+index).removeAttr('disabled');
$('.submitBtn[data-index=' + index + ']').show();
$(this).html($(this).data('icon-cancel'));
$(this).data('state', 'cancel');
} else {
$('#prePromptInput_'+index).attr('disabled', 'disabled');
$('#postPromptInput_'+index).attr('disabled', 'disabled');
$('#blacklist_'+index).attr('disabled', 'disabled');
$('.submitBtn[data-index=' + index + ']').hide();
$(this).html($(this).data('icon-edit'));
$(this).data('state', 'edit');
}
});
});
</script>";
print $out;
print '<br>';

View File

@ -46,6 +46,10 @@ require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/ai/class/ai.class.php';
if (!isModEnabled('ai')) {
accessforbidden('Module AI not enabled');
}
/*
* View
@ -63,7 +67,7 @@ if (is_null($jsonData)) {
$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 @@ if (is_array($generatedContent) && $generatedContent['error']) {
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)
}
}

View File

@ -71,11 +71,11 @@ class Ai
/**
* 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 @@ class Ai
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 @@ class Ai
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') {

View File

@ -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 @@ class FormMail extends Form
$htmlContent = preg_replace('/[^a-z0-9_]/', '', $htmlContent);
$out = '<tr id="ai_input" class="hidden">';
$out .= '<td>';
//$out .= $form->textwithpicto($langs->trans('HelpWithAI'), $langs->trans("YouCanMakeSomeInstructionForEmail"));
$out .= '</td>';
$out .= '<td>';
$out .= '<input type="text" class="quatrevingtpercent" id="ai_instructions" name="instruction" placeholder="'.$langs->trans("EnterYourAIPromptHere").'..." />';
$out .= '<input id="generate_button" type="button" class="button smallpaddingimp" value="'.$langs->trans('Generate').'"/>';
$out .= '<div id="ai_status_message" class="fieldrequired hideobject marginrightonly margintoponly">';
$out = '<div id="ai_input'.$htmlContent.'" class="hidden">';
$out .= '<input type="text" class="quatrevingtpercent" id="ai_instructions'.$htmlContent.'" name="instruction" placeholder="'.$langs->trans("EnterYourAIPromptHere").'..." />';
$out .= '<input id="generate_button'.$htmlContent.'" type="button" class="button smallpaddingimp" value="'.$langs->trans('Generate').'"/>';
$out .= '<div id="ai_status_message'.$htmlContent.'" class="fieldrequired hideobject marginrightonly margintoponly">';
$out .= '<i class="fa fa-spinner fa-spin fa-2x fa-fw valignmiddle marginrightonly"></i>'.$langs->trans("AIProcessingPleaseWait", getDolGlobalString('AI_API_SERVICE', 'chatgpt'));
$out .= '</div>';
$out .= "</td></tr>\n";
$out .= "</div>\n";
$out .= "<script type='text/javascript'>
$(document).ready(function() {
// for keydown
$('#ai_instructions').keydown(function(event) {
$('#ai_instructions".$htmlContent."').keydown(function(event) {
if (event.keyCode === 13) {
event.preventDefault();
$('#generate_button').click();
$('#generate_button".$htmlContent."').click();
}
});
$('#generate_button').click(function() {
console.log('We click on generate ai button');
$('#generate_button".$htmlContent."').click(function() {
console.log('We click on generate_button".$htmlContent." ai button');
var instructions = $('#ai_instructions').val();
var instructions = $('#ai_instructions".$htmlContent."').val();
var timeoutfinished = 0;
var apicallfinished = 0;
$('#ai_status_message').show();
$('#ai_status_message".$htmlContent."').show();
$('.icon-container .loader').show();
setTimeout(function() {
timeoutfinished = 1;
if (apicallfinished) {
$('#ai_status_message').hide();
$('#ai_status_message".$htmlContent."').hide();
}
}, 2000);
@ -1474,9 +1470,10 @@ class FormMail extends Form
'instructions': instructions, /* the prompt string */
}),
success: function(response) {
console.log('Add response into field \'".$htmlContent."\': '+response);
console.log('Add response into field \'#".$htmlContent."\': '+response);
jQuery('#".$htmlContent."').val(response);
jQuery('#".$htmlContent."').val(response); // If #htmlcontent is a input name or textarea
jQuery('#".$htmlContent."').html(response); // If #htmlContent is a div
//jQuery('#".$htmlContent."preview').val(response);
if (CKEDITOR.instances) {
@ -1492,17 +1489,17 @@ class FormMail extends Form
}
// remove readonly
$('#ai_instructions').val('');
$('#ai_instructions".$htmlContent."').val('');
apicallfinished = 1;
if (timeoutfinished) {
$('#ai_status_message').hide();
$('#ai_status_message".$htmlContent."').hide();
}
},
error: function(xhr, status, error) {
alert(error);
console.error('error ajax', status, error);
$('#ai_status_message').hide();
$('#ai_status_message".$htmlContent."').hide();
}
});

View File

@ -1878,8 +1878,8 @@ function dol_escape_xml($stringtoescape)
}
/**
* Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on HTML page
* To use text that is not HTML content inside an attribute, use can simply only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute().
* Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on HTML page.
* To use text that is not HTML content inside an attribute, you can simply use only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute().
*
* @param string $s String to print
* @return string String ready for HTML output
@ -1889,6 +1889,18 @@ function dolPrintLabel($s)
return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', 0, 1);
}
/**
* Return a string label (possible on several lines and that should not contains any HTML) ready to be output on HTML page.
* To use text that is not HTML content inside an attribute, you can simply use only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute().
*
* @param string $s String to print
* @return string String ready for HTML output
*/
function dolPrintText($s)
{
return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1);
}
/**
* Return a string (that can be on several lines) ready to be output on a HTML page.
* To output a text inside an attribute, you can use dolPrintHTMLForAttribute() or dolPrintHTMLForTextArea() inside a textarea
@ -1918,7 +1930,8 @@ function dolPrintHTMLForAttribute($s)
}
/**
* Return a string ready to be output on input textarea
* Return a string ready to be output on input textarea.
* Differs from dolPrintHTML because all tags are escape. With dolPrintHTML, all tags except common one are escaped.
*
* @param string $s String to print
* @param int $allowiframe Allow iframe tags
@ -1951,7 +1964,7 @@ function dolPrintPassword($s)
* @param string $stringtoescape String to escape
* @param int $keepb 1=Replace b tags with escaped value (except if in $noescapetags), 0=Remove them completely
* @param int $keepn 1=Preserve \r\n strings, 0=Replace them with escaped value, -1=Remove them. Set to 1 when escaping for a <textarea>.
* @param string $noescapetags '' or 'common' or list of tags to not escape.
* @param string $noescapetags ''= or 'common' or list of tags to not escape.
* @param int $escapeonlyhtmltags 1=Escape only html tags, not the special chars like accents.
* @param int $cleanalsojavascript Clean also javascript. @TODO switch this option to 1 by default.
* @return string Escaped string

View File

@ -18,10 +18,10 @@
* $conf
* $formmail
* $formwebsite (optional)
* $showlinktolayout
* $showlinktolayoutlabel
* $showlinktolayout=0|1
* $showlinktolayoutlabel='...'
* $showlinktoai ('' or 'textgeneration', 'textgenerationemail', 'textgenerationwebpage', ...)
* $showlinktoailabel
* $showlinktoailabel='...'
* $htmlname
*/
@ -31,6 +31,11 @@ if (empty($conf) || !is_object($conf)) {
exit(1);
}
if (empty($htmlname)) {
print 'Parameter htmlname not defined.';
exit(1);
}
?>
<!-- BEGIN PHP TEMPLATE formlayoutai.tpl.php -->
<?php
@ -57,19 +62,19 @@ if ($showlinktolayout) {
}
// Add link to add AI content
if ($showlinktoai) {
$out .= '<a href="#" id="linkforaiprompt" class="reposition notasortlink inline-block alink marginrightonly">';
$out .= '<a href="#" id="linkforaiprompt'.$showlinktoai.'" class="reposition notasortlink inline-block alink marginrightonly">';
$out .= img_picto($showlinktoailabel, 'ai', 'class="paddingrightonly"');
$out .= $showlinktoailabel.'...';
$out .= '</a>';
$out .= '<script>
$(document).ready(function() {
$("#linkforaiprompt").click(function() {
console.log("We click on linkforaiprompt");
$("#linkforaiprompt'.$showlinktoai.'").click(function() {
console.log("formlayoutai.tpl: We click on linkforaiprompt'.$showlinktoai.', we toggle #ai_input'.$showlinktoai.'");
event.preventDefault();
jQuery("#ai_input").toggle();
jQuery("#ai_input'.$htmlname.'").toggle();
jQuery("#template-selector").hide();
if (!jQuery("ai_input").is(":hidden")) {
if (!jQuery("#ai_input'.$htmlname.'").is(":hidden")) {
console.log("Set focus on input field");
jQuery("#ai_instructions").focus();
if (!jQuery("pageContent").is(":hidden")) { // May exists for website page only