NEW Add a test mode for AI setup

This commit is contained in:
ldestailleur 2025-02-16 22:45:14 +01:00
parent bc1dbae6b0
commit 47d59c7938
8 changed files with 99 additions and 47 deletions

View File

@ -305,6 +305,7 @@ if ($action == 'edit' || $action == 'deleteproperty') {
$out .= $form->buttonsSaveCancel("Add", "");
$out .= '</form>';
$out .= '<br><br><br>';
print $out;
@ -365,7 +366,7 @@ if ($action == 'edit' || $action == 'create' || $action == 'deleteproperty') {
$out .= '<tr>';
$out .= '<td></td>';
$out .= '<td>';
$out .= '<input type="submit" class="button small submitBtn reposition" name="modify" data-index="'.$key.'" 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("Save")).'"/>';
$out .= ' &nbsp; ';
include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';

View File

@ -176,17 +176,22 @@ $out = '';
if ($functioncode) {
$labeloffeature = empty($arrayofaifeatures[GETPOST('functioncode')]['label']) ? 'Undefined' : $arrayofaifeatures[GETPOST('functioncode')]['label'];
$out .= $langs->trans("Test").' '.$labeloffeature.'...<br><br>';
//$out .= $langs->trans("Test").' '.$labeloffeature.'...<br><br>';
if (GETPOST('functioncode') == 'textgenerationemail') {
$showlinktoai = 1;
$key = 'textgenerationemail'; // The HTML ID of field to fill
include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
$showlinktoai = $key; // 'textgeneration', 'imagegeneration', ...
$showlinktoailabel = $langs->trans("Test").' '.$labeloffeature;
$showlinktolayout = 0;
$formmail = new FormMail($db);
$htmlname = $key;
// Fill $out
include DOL_DOCUMENT_ROOT.'/core/tpl/formlayoutai.tpl.php';
$neweditor = new DolEditor('aicontenttotest', $content);
$out .= $neweditor->Create(1);
$out .= $form->buttonsSaveCancel("Test");
$out .= '<div id="'.$key.'"></div>';
} else {
$out .= $langs->trans("FeatureNotYetAvailable").'<br><br>';
$functioncode = '';

View File

@ -81,15 +81,17 @@ $format = empty($jsonData['format']) ? '' : $jsonData['format'];
$generatedContent = $ai->generateContent($instructions, 'auto', $function, $format);
if (is_array($generatedContent) && $generatedContent['error']) {
if (is_null($generatedContent) || (is_array($generatedContent) && $generatedContent['error'])) {
// Output error
if (!empty($generatedContent['code']) && $generatedContent['code'] == 429) {
print "Quota or allowed period exceeded. Retry Later !";
} elseif ($generatedContent['code'] >= 400) {
} elseif (!empty($generatedContent['code']) && $generatedContent['code'] >= 400) {
print "Error : " . $generatedContent['message'];
print '<br><a href="'.DOL_MAIN_URL_ROOT.'/ai/admin/setup.php">'.$langs->trans('ErrorGoToModuleSetup').'</a>';
} else {
} elseif (!empty($generatedContent['message'])) {
print "Error returned by API call: " . $generatedContent['message'];
} else {
print "Error API returned no answer";
}
} else {
if ($function == 'textgenerationemail' || $function == 'textgenerationwebpage') {

View File

@ -80,6 +80,8 @@ class Ai
*/
public function generateContent($instructions, $model = 'auto', $function = 'textgeneration', $format = '')
{
global $dolibarr_main_data_root;
if (empty($this->apiKey)) {
return array('error' => true, 'message' => 'API key is not defined for the AI enabled service ('.$this->apiService.')');
}
@ -199,7 +201,7 @@ class Ai
$postPrompt = $configurations[$function]['postPrompt'];
}
}
$fullInstructions = ($prePrompt ? $prePrompt.' ' : '').$instructions.($postPrompt ? '. '.$postPrompt : '');
$fullInstructions = $instructions.($postPrompt ? (preg_match('/[\.\!\?]$/', $instructions) ? '' : '.').' '.$postPrompt : '');
// Set payload string
/*{
@ -224,18 +226,46 @@ class Ai
"temperature": 0.7,
"top_p": 0.95
}*/
$payload = json_encode([
'messages' => [
['role' => 'user', 'content' => $fullInstructions]
],
'model' => $model,
//'stream' => false
]);
$headers = ([
$arrayforpayload = array(
'messages' => array(array('role' => 'user', 'content' => $fullInstructions)),
'model' => $model,
);
// Add a system message
$addDateTimeContext = 0;
if ($addDateTimeContext) {
$prePrompt = ($prePrompt ? $prePrompt.(preg_match('/[\.\!\?]$/', $prePrompt) ? '' : '.').' ' : '').'Today we are '.dol_print_date(dol_now(), 'dayhourtext');
}
$arrayforpayload['messages'][] = array('role' => 'system', 'content' => $prePrompt);
/*
$arrayforpayload['temperature'] = 0.7;
$arrayforpayload['max_tokens'] = -1;
$arrayforpayload['stream'] = false;
*/
$payload = json_encode($arrayforpayload);
$headers = array(
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
]);
);
if (getDolGlobalString("AI_DEBUG")) {
if (@is_writable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
$outputfile = $dolibarr_main_data_root."/dolibarr_ai.log";
$fp = fopen($outputfile, "w"); // overwrite
if ($fp) {
fwrite($fp, var_export($headers, true)."\n");
fwrite($fp, var_export($payload, true)."\n");
fclose($fp);
dolChmod($outputfile);
}
}
}
$localurl = 2; // Accept both local and external endpoints
$response = getURLContent($this->apiEndpoint, 'POST', $payload, 1, $headers, array('http', 'https'), $localurl);
@ -248,7 +278,17 @@ class Ai
}
if (getDolGlobalString("AI_DEBUG")) {
dol_syslog("response content = ".var_export($response['content'], true));
if (@is_writable($dolibarr_main_data_root)) { // Avoid fatal error on fopen with open_basedir
$outputfile = $dolibarr_main_data_root."/dolibarr_ai.log";
$fp = fopen($outputfile, "a");
if ($fp) {
fwrite($fp, var_export((empty($response['content']) ? 'No content result' : $response['content']), true)."\n");
fclose($fp);
dolChmod($outputfile);
}
}
}
// Decode JSON response

View File

@ -1483,24 +1483,26 @@ class CMailFile
$outputfile = $dolibarr_main_data_root."/dolibarr_mail.log";
$fp = fopen($outputfile, "w"); // overwrite
if ($this->sendmode == 'mail') {
fwrite($fp, $this->headers);
fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log
fwrite($fp, $this->message);
} elseif ($this->sendmode == 'smtps') {
fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
} elseif ($this->sendmode == 'swiftmailer') {
fwrite($fp, "smtpheader=\n".$this->message->getHeaders()->toString()."\n");
fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
}
if ($fp) {
if ($this->sendmode == 'mail') {
fwrite($fp, $this->headers);
fwrite($fp, $this->eol); // This eol is added by the mail function, so we add it in log
fwrite($fp, $this->message);
} elseif ($this->sendmode == 'smtps') {
fwrite($fp, $this->smtps->log); // this->smtps->log is filled only if MAIN_MAIL_DEBUG was set to on
} elseif ($this->sendmode == 'swiftmailer') {
fwrite($fp, "smtpheader=\n".$this->message->getHeaders()->toString()."\n");
fwrite($fp, $this->logger->dump()); // this->logger is filled only if MAIN_MAIL_DEBUG was set to on
}
fclose($fp);
dolChmod($outputfile);
fclose($fp);
dolChmod($outputfile);
// Move dolibarr_mail.log into a dolibarr_mail.log.v123456789
if (getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) {
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
archiveOrBackupFile($outputfile, getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE'));
// Move dolibarr_mail.log into a dolibarr_mail.log.v123456789
if (getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE')) {
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
archiveOrBackupFile($outputfile, getDolGlobalInt('MAIN_MAIL_DEBUG_LOG_WITH_DATE'));
}
}
}
}

View File

@ -1493,6 +1493,7 @@ class FormMail extends Form
* @param string $format Format for output ('', 'html', ...)
* @param string $htmlContent HTML name of WYSIWYG field
* @return string HTML code to ask AI instruction and autofill result
* TODO Move into a file html.formai.class.php
*/
public function getSectionForAIPrompt($function = 'textgeneration', $format = '', $htmlContent = 'message')
{
@ -1502,7 +1503,7 @@ class FormMail extends Form
$htmlContent = preg_replace('/[^a-z0-9_]/', '', $htmlContent);
$out = '<div id="ai_input'.$htmlContent.'" class="hidden paddingtop paddingbottom">';
$out = '<div id="ai_input'.$htmlContent.'" class="ai_input'.$htmlContent.' hidden paddingtop paddingbottom">';
$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">';
@ -1526,7 +1527,7 @@ class FormMail extends Form
});
$('#generate_button".$htmlContent."').click(function() {
console.log('We click on generate_button".$htmlContent." ai button');
console.log('We click on generate_button".$htmlContent." ai button, so we make an ajax on url /ai/ajax/generate_content.php');
var instructions = $('#ai_instructions".$htmlContent."').val();
var timeoutfinished = 0;
@ -1659,7 +1660,7 @@ class FormMail extends Form
$websitepage = new WebsitePage($this->db);
$arrayofblogs = $websitepage->fetchAll('', 'DESC', 'date_creation', 0, 0, array('type_container' => 'blogpost'));
$out = '<div id="template-selector" class="email-layout-container hidden" style="display:none;">';
$out = '<div id="template-selector" class="template-selector email-layout-container hidden" style="display:none;">';
// Define list of email layouts to use
$layouts = array(

View File

@ -60,7 +60,7 @@ if (!isset($out)) { // Init to empty string if not defined
}
// Add link to add layout
if ($showlinktolayout) {
if ($showlinktolayout) { // May be set only if MAIN_EMAIL_USE_LAYOUT is set
$out .= '<a href="#" id="linkforlayouttemplates" class="notasortlink inline-block alink marginrightonly">';
$out .= img_picto($showlinktolayoutlabel, 'layout', 'class="paddingrightonly"');
$out .= $showlinktolayoutlabel.'...';
@ -69,10 +69,10 @@ if ($showlinktolayout) {
$out .= '<script>
$(document).ready(function() {
$("#linkforlayouttemplates").click(function() {
console.log("We click on linkforlayouttemplates");
console.log("We click on linkforlayouttemplates, we toggle .template-selector");
event.preventDefault();
jQuery("#template-selector").toggle();
jQuery("#ai_input'.$htmlname.'").hide();
jQuery(".template-selector").toggle();
jQuery(".ai_input'.$htmlname.'").hide();
jQuery("#pageContent").show(); // May exists for website page only
});
});
@ -89,10 +89,10 @@ if ($showlinktoai) {
$out .= '<script>
$(document).ready(function() {
$("#linkforaiprompt'.$showlinktoai.'").click(function() {
console.log("formlayoutai.tpl: We click on linkforaiprompt'.$showlinktoai.', we toggle #ai_input'.$showlinktoai.'");
console.log("formlayoutai.tpl: We click on linkforaiprompt'.$showlinktoai.', we toggle .ai_input'.$showlinktoai.'");
event.preventDefault();
jQuery("#ai_input'.$htmlname.'").toggle();
jQuery("#template-selector").hide();
jQuery(".ai_input'.$htmlname.'").toggle();
jQuery(".template-selector").hide();
jQuery(".email-layout-container").hide();
if (!jQuery("#ai_input'.$htmlname.'").is(":hidden")) {
console.log("Set focus on input field #ai_instructions'.$htmlname.'");

View File

@ -2614,3 +2614,4 @@ Privileges=Privileges
FieldsLinked=Fields Linked
PDF_XXX_SHOW_PRICE_INCL_TAX=Show column Price including tax
AvailableWithSomePDFTemplatesOnly=Feature not supported on old PDF templates
SelectFeatureToTest=Select the feature to test