dolibarr 25.0.0-alpha
assistant.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2004-2017 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2022 Alice Adminson <aadminson@example.com>
4 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
6 * Coryright (C) 2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
7 * Copyright (C) 2026 Nick Fragoulis
8 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY, without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
30require '../../main.inc.php';
39require_once DOL_DOCUMENT_ROOT . "/core/lib/admin.lib.php";
40require_once DOL_DOCUMENT_ROOT . "/core/class/html.form.class.php";
41require_once DOL_DOCUMENT_ROOT . "/core/class/doleditor.class.php";
42require_once DOL_DOCUMENT_ROOT . "/ai/lib/ai.lib.php";
43require_once DOL_DOCUMENT_ROOT . "/core/lib/functions2.lib.php";
44require_once DOL_DOCUMENT_ROOT . "/core/lib/security.lib.php";
45
46
47$langs->loadLangs(array("admin", "website", "other"));
48
49// Access control
50if (!$user->admin) {
52}
53if (!isModEnabled('ai')) {
54 accessforbidden('Module AI not activated.');
55}
56
57// Parameters
58$action = GETPOST('action', 'aZ09');
59$backtopage = GETPOST('backtopage', 'alpha');
60
61
66// Main Settings
67if ($action == 'update') {
68 $error = 0;
69
70 if (GETPOSTISSET('AI_ASK_FOR_CONFIRMATION')) {
71 $res = dolibarr_set_const($db, "AI_ASK_FOR_CONFIRMATION", GETPOSTINT("AI_ASK_FOR_CONFIRMATION"), 'int', 0, '', $conf->entity);
72 if ($res <= 0) $error++;
73 }
74
75 if (GETPOSTISSET('AI_CHAT_CONTEXT_AUTO_EXCHANGES')) {
76 $res = dolibarr_set_const($db, "AI_CHAT_CONTEXT_AUTO_EXCHANGES", max(0, GETPOSTINT("AI_CHAT_CONTEXT_AUTO_EXCHANGES")), 'int', 0, '', $conf->entity);
77 if ($res <= 0) {
78 $error++;
79 }
80 }
81
82 if (GETPOSTISSET('AI_LOG_RETENTION')) {
83 $res = dolibarr_set_const($db, "AI_LOG_RETENTION", GETPOST("AI_LOG_RETENTION"), 'int', 0, '', $conf->entity);
84 if ($res <= 0) {
85 $error++;
86 }
87 }
88
89 if (GETPOSTISSET('AI_DEFAULT_INPUT_MODE')) {
90 $res = dolibarr_set_const($db, "AI_DEFAULT_INPUT_MODE", GETPOST("AI_DEFAULT_INPUT_MODE"), 'chaine', 0, '', $conf->entity);
91 if ($res <= 0) {
92 $error++;
93 }
94 }
95
96 if (GETPOSTISSET('AI_INTENT_PROMPT')) {
97 $res = dolibarr_set_const($db, "AI_INTENT_PROMPT", GETPOST("AI_INTENT_PROMPT"), 'chaine', 0, '', $conf->entity);
98 if ($res <= 0) {
99 $error++;
100 }
101 }
102
103 if ($error) {
104 setEventMessages($langs->trans("ErrorSavingSettings"), null, 'errors');
105 } else {
106 setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
107 }
108
109 header("Location: ".$_SERVER["PHP_SELF"]."?mainmenu=home");
110 exit;
111}
112
113// Test connection
114if ($action == 'test_provider') {
115 $service = GETPOST('service_key', 'aZ09');
116 if ($service) {
117 $credential = getDolGlobalString('AI_API_' . strtoupper($service) . '_KEY');
118 $url = getDolGlobalString('AI_API_' . strtoupper($service) . '_URL');
119
120 // Decrypt if needed
121 if (preg_match('/^crypted:/', $credential)) {
122 $credential = dol_decode(substr($credential, 8));
123 } elseif (preg_match('/^dolcrypt:/', $credential)) {
124 $credential = dolDecrypt($credential, '');
125 }
126
127 // Only proceed if the key is valid (decrypted or not encrypted)
128 if ($credential !== null) {
129 $res = testAIConnection($service, $credential, $url);
130
131 if ($res['success']) {
132 setEventMessages($langs->trans("ConnectionSuccessful") . $res['message'], null, 'mesgs');
133 } else {
134 setEventMessages($langs->trans("ConnectionFailed") . $res['message'], null, 'errors');
135 }
136 }
137 }
138}
139
140// External Access Settings
141if ($action == 'update_external') {
142 $error = 0;
143
144 $user_id = GETPOSTINT("AI_MCP_USER_ID");
145 if (GETPOSTISSET('AI_MCP_USER_ID')) {
146 $res = dolibarr_set_const($db, "AI_MCP_USER_ID", $user_id, 'int', 0, '', $conf->entity);
147 if ($res <= 0) {
148 $error++;
149 }
150 }
151
152 if ($error) {
153 setEventMessages($langs->trans("ErrorSavingSettings"), null, 'errors');
154 } else {
155 setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
156 }
157
158 header("Location: ".$_SERVER["PHP_SELF"]."?mainmenu=home");
159 exit;
160}
161
162// Generate New API Key
163if ($action == 'generate_key') {
164 $newKey = dolGetRandomBytes(64);
165
166 if (empty($newKey)) {
167 setEventMessages($langs->trans("KeyGenerationFailed"), null, 'errors');
168 } else {
169 if (dolibarr_set_const($db, 'AI_MCP_API_KEY', $newKey, 'chaine', 0, '', $conf->entity) > 0) {
170 setEventMessages($langs->trans("KeyGenerationSuccessfull"), null, 'mesgs');
171 } else {
172 setEventMessages($langs->trans("KeySaveFailed"), null, 'errors');
173 }
174 }
175}
176
177
178/*
179 * VIEW
180 */
181
183$title = "AiSetup";
184
185llxHeader('', $langs->trans($title), $help_url, '', 0, 0, '', '', '', 'mod-ai page-admin');
186
187$linkback = '<a href="'.($backtopage ? $backtopage : DOL_URL_ROOT.'/admin/modules.php?restore_lastsearch_values=1').'">'.img_picto($langs->trans("BackToModuleList"), 'back', 'class="pictofixedwidth"').'<span class="hideonsmartphone">'.$langs->trans("BackToModuleList").'</span></a>';
188
189print load_fiche_titre($langs->trans($title), $linkback, 'title_setup');
190
191
192$head = aiAdminPrepareHead();
193print dol_get_fiche_head($head, 'assistant', "MCP Server", -1, "ai");
194
195//print '<span class="opacitymedium">' . $langs->trans("ConfigAssistantHelp") . '</span><br><br>';
196
197$form = new Form($db);
198
199// Load Current Values
200$apiKey = getDolGlobalString('AI_MCP_API_KEY');
201
202// Default Prompt Logic
203$defaultPromptText = " ROLE: Dolibarr ERP AI.
204GOAL: Map user intent to specific JSON commands.
205
206CONSTRAINTS:
2071. OUTPUT: Single valid JSON object ONLY. No Markdown. No text.
208 Format: {\"tool\": \"tool_name\", \"arguments\": {\"argument_name\": \"argument_value\", ...}}
2092. TOOLS: Use ONLY provided tools. If no tool or thirdparty fits, you must first use 'respond_to_user' to inform user.
2103. ARGS: strict adherence to schema. Do not invent parameters.
2114. MISSING INFO: If a required argument (like an ID) is missing, use 'ask_for_clarification'.
2125. SAFETY: For DELETE/UPDATE actions, you MUST use 'ask_for_confirmation'.";
213
214$currentPrompt = getDolGlobalString('AI_INTENT_PROMPT', $defaultPromptText);
215
216// Settings
217print '<div class="neutral">';
218//print '<table class="noborder centpercent">';
219
220// Enable/Disable
221//print '<tr class="oddeven">';
222//print '<td class="titlefield" width="30%">'
223print $form->textwithpicto($langs->trans('EnableAssistant'), $langs->trans('DisableAssistant'));
224//print '</td>';
225//print '<td>';
226print ' &nbsp; ';
227print ajax_constantonoff('AI_ASSISTANT_ENABLED', array(), null, 0, 0, 1);
228//print ' <span class="opacitymedium">' . $langs->trans('DisableMCPAI') . '</span>';
229//print '</td>';
230//print '</tr>';
231
232//print '</table>';
233print '</div>';
234
235print '<br>';
236print '<br>';
237
238if (getDolGlobalString('AI_ASSISTANT_ENABLED')) {
239 print load_fiche_titre($langs->trans("PrivateModeTitle"), '', 'fas fa-lock');
240
241 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
242 print '<input type="hidden" name="token" value="'.newToken().'">';
243 print '<input type="hidden" name="action" value="update">';
244
245 print '<div class="div-table-responsive-no-min">';
246 print '<table class="noborder centpercent">';
247
248 // Input Mode
249 print '<tr class="oddeven">';
250 print '<td>';
251 print $form->textwithpicto('Default Interface', $langs->trans("InputMethodHelp"));
252 print '</td>';
253 print '<td>';
254 $input_modes = [
255 'text' => $langs->trans('OptionTextOnly'),
256 'native' => $langs->trans('OptionCloudFast') . ' - ' . $langs->trans('OptionCloudFasthelp'),
257 'whisper' => $langs->trans('OptionWhisperLocal') . ' - ' . $langs->trans('OptionWhisperLocalhelp')
258 ];
259 print $form->selectarray('AI_DEFAULT_INPUT_MODE', $input_modes, getDolGlobalString('AI_DEFAULT_INPUT_MODE'), 0, 0, 0);
260 print '</td>';
261 print '</tr>';
262
263 // Enhanced Privacy control
264 print '<tr class="oddeven">';
265 print '<td>' . $langs->trans('ObfuscatePIIData') . '</td>';
266 print '<td>';
267 print ajax_constantonoff('AI_PRIVACY_REDACTION');
268 print ' &nbsp; <span class="opacitymedium">' . $langs->trans("RedactionHelp") . '</span>';
269 print '</td>';
270 print '</tr>';
271
272 // Confirmation Level
273 print '<tr class="oddeven">';
274 print '<td>' . $form->textwithpicto($langs->trans("AskConfirmation"), $langs->trans("AskConfirmationHelp")).'</td>';
275 print '<td>';
276 $confirmation_options = [
277 '0' => $langs->trans("ConfirmNever"),
278 '1' => $langs->trans("ConfirmWriteOnly"),
279 '2' => $langs->trans("ConfirmAlways")
280 ];
281 print $form->selectarray('AI_ASK_FOR_CONFIRMATION', $confirmation_options, getDolGlobalInt('AI_ASK_FOR_CONFIRMATION', 1), 0, 0, 0);
282 print '</td>';
283 print '</tr>';
284
285 // Second step: a write asked, a read answered -> run the read, then the write
286 print '<tr class="oddeven">';
287 print '<td>' . $form->textwithpicto($langs->trans("AIChatTwoStepWrite"), $langs->trans("AIChatTwoStepWriteHelp")) . '</td>';
288 print '<td>';
289 print ajax_constantonoff('AI_CHAT_TWO_STEP_WRITE');
290 print '</td>';
291 print '</tr>';
292
293 // Context window of the chat
294 print '<tr class="oddeven">';
295 print '<td>' . $form->textwithpicto($langs->trans("AIContextAutoExchanges"), $langs->trans("AIContextAutoExchangesHelp")) . '</td>';
296 print '<td><input class="width50" type="number" min="0" name="AI_CHAT_CONTEXT_AUTO_EXCHANGES" value="' . getDolGlobalInt('AI_CHAT_CONTEXT_AUTO_EXCHANGES', 0) . '"></td>';
297 print '</tr>';
298
299 // Logging
300 print '<tr class="oddeven">';
301 print '<td>' . $langs->trans('EnableLogging') . '</td>';
302 print '<td>';
303 print ajax_constantonoff('AI_LOG_REQUESTS');
304 print ' &nbsp; <a href="' . DOL_URL_ROOT . '/ai/admin/log_viewer.php" target="_blank" style="padding-top: 4px; padding-bottom: 4px;">'.$langs->trans("ViewLogs").'</a>';
305 print '</td>';
306 print '</tr>';
307
308 print '<tr class="oddeven">';
309 print '<td>' . $langs->trans("LogRetention") . '</td>';
310 print '<td><input class="width50" type="number" name="AI_LOG_RETENTION" value="' . getDolGlobalInt('AI_LOG_RETENTION', 30) . '"> (0 = Forever)</td>';
311 print '</tr>';
312
313 // System Prompt
314 print '<tr class="oddeven">';
315 print '<td colspan="2">';
316 print $form->textwithpicto($langs->trans("SystemPrompt"), $langs->trans("SystemPromptHelp")) . '<br><br>';
317 $doleditor = new DolEditor('AI_INTENT_PROMPT', $currentPrompt, '', 250, 'dolibarr_notes', 'In', false, false, true, ROWS_8, '90%');
318 $doleditor->Create();
319 print '</td>';
320 print '</tr>';
321
322 print '</table>';
323 print '</div>';
324
325 print '<div class="center"><input type="submit" class="button" value="'.$langs->trans("Save").'"></div>';
326 print '</form>';
327
328 print '<br>';
329 print '<br>';
330
331
332 // AI Provider Config and Connection testing
333 $services = getListOfAIServices();
334 $currentService = getDolGlobalString('AI_API_SERVICE');
335
336 print load_fiche_titre($langs->trans("AIProviderConfigTitle"), '', 'fa fa-plug');
337
338 if ((string) $currentService == '-1') {
339 print '<div class="warning">'.$langs->trans("NoAIProviderSelected").' <a href="'.dol_buildpath('/ai/admin/setup.php', 1).'">'.$langs->trans("ConfigureHere").'</a></div>';
340 } else {
341 print '<div class="div-table-responsive-no-min">';
342 print '<table class="noborder centpercent">';
343
344 print '<tr class="oddeven"><td class="titlefield">'.$langs->trans("AIProvider").'</td><td>'.$services[$currentService]['label'].'</td></tr>';
345
346 $prefix = 'AI_API_'.strtoupper($currentService);
347 $modelVal = getDolGlobalString($prefix.'_MODEL_TEXT', $services[$currentService]['textgeneration']['default']);
348
349 print '<tr class="oddeven"><td>'.$langs->trans("AI_API_MODEL").'</td><td>'.$modelVal.'</td></tr>';
350 print '</table></div>';
351
352 print '<div class="center">';
353
354 if ($currentService && $currentService !== '-1') {
355 print '<a class="reposition button smallpaddingimp" href="'.$_SERVER['PHP_SELF'].'?action=test_provider&token='.newToken().'&service_key='.urlencode($currentService).'">'.$langs->trans('TestConnection').'</a>';
356 }
357
358 print '</div>';
359 }
360
361 print '<br><br>';
362
363 // External Access Configuration
364 /*
365 print load_fiche_titre($langs->trans("AiMcpExternalAccess"), '', 'fas fa-lock-open');
366
367 print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
368 print '<input type="hidden" name="token" value="'.newToken().'">';
369 print '<input type="hidden" name="action" value="update_external">';
370
371 print '<div class="div-table-responsive-no-min">';
372 print '<table class="noborder centpercent">';
373
374 // Service User
375 print '<tr class="oddeven">';
376 print '<td>Service User <span class="fa fa-info-circle" title="' . $langs->trans("UserPermissionsTooltip") . '"></span></td>';
377 print '<td>';
378 print '<div style="display: flex; align-items: center;">';
379 print $form->select_dolusers(getDolGlobalInt('AI_MCP_USER_ID'), 'AI_MCP_USER_ID', 1);
380 print ' <input type="submit" class="button" value="'.$langs->trans("Save").'" style="margin-left: 20px;">';
381 print '</div>';
382 print '<span class="opacitymedium small">' . $langs->trans("DedicatedUserRecommendation") . '</span>';
383 print '</td>';
384 print '</tr>';
385
386 print '<tr class="oddeven">';
387 print '<td width="30%">API Key</td>';
388 print '<td>';
389 if ($apiKey) {
390 print '<input type="text" id="apikey" value="'.$apiKey.'" readonly style="width:400px; padding:6px; background:#f4f4f4; border:1px solid #ccc; color:#555;">';
391 print ' <a class="button smallpaddingimp" href="'.$_SERVER["PHP_SELF"].'?action=generate_key&token='.newToken().'">Generate New Key</a>';
392 } else {
393 print '<span class="opacitymedium">' . $langs->trans("NoKeyWarning") . '</span>';
394 print ' <a class="button smallpaddingimp" href="' . $_SERVER["PHP_SELF"] . '?action=generate_key&token=' . newToken() . '">' . $langs->trans("GenerateKey") . '</a>';
395 }
396 print '</td>';
397 print '</tr>';
398
399 $endpoint = dol_buildpath('/ai/server/mcp_server.php', 3);
400
401 print '<tr class="oddeven">';
402 print '<td>Endpoint URL</td>';
403 print '<td>';
404 print '<input type="text" id="endpoint" value="'.$endpoint.'" readonly style="width:600px; border:none; background:transparent;">';
405 print '</td>';
406 print '</tr>';
407
408 print '</table>';
409 print '</div>';
410
411 print '</form>';
412
413 // Configuration Examples
414 print '<br>';
415 print '<div style="background:#fcfcfc; border:1px solid #eee; padding:15px; border-radius:5px;">';
416 print '<strong>' . $langs->trans("ClaudeDesktopConfig") . '</strong><br>';
417 print '<pre style="background:#333; color:#fff; padding:10px; border-radius:4px; overflow:auto; margin-top:10px;">';
418 echo htmlspecialchars('
419 {
420 "mcpServers": {
421 "dolibarr": {
422 "command": "node",
423 "args": ["/path/to/mcp-bridge.js"],
424 "env": {
425 "DOLIBARR_URL": "'.$endpoint.'",
426 "DOLIBARR_API_KEY": "'.($apiKey ? $apiKey : "YOUR_KEY_HERE").'"
427 }
428 }
429 }
430 }');
431 print '</pre>';
432 print '</div>';
433 */
434}
435
436print dol_get_fiche_end();
437
438llxFooter();
439$db->close();
dolibarr_set_const($db, $name, $value, $type='chaine', $visible=0, $note='', $entity=1)
Insert a parameter (key,value) into database (delete old key then insert it again).
testAIConnection(string $service, string $key, string $url)
Tests the connection to an AI service using its API key and URL by sending message "Hello".
Definition ai.lib.php:213
aiAdminPrepareHead()
Prepare admin pages header.
Definition ai.lib.php:531
getListOfAIServices()
Get list of available ai services.
Definition ai.lib.php:69
if( $action=='update') if($action=='test_provider') if( $action=='update_external') if($action=='generate_key') $help_url
ACTIONS.
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage a WYSIWYG editor.
Class to manage generation of HTML components Only common components must be here.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:738
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
dolGetRandomBytes($length)
Return a string of random bytes (hexa string) with length = $length for cryptographic purposes.
dol_decode($chain, $key='1')
Decode a base 64 encoded + specific delta change.
accessforbidden($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program.
dolDecrypt($chain, $key='', $patterntotest='')
Decode a string with a symmetric encryption.