data-primals-engine 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // data-primals-engine/src/index.js
1
+ // data-primals-engine/index
2
2
 
3
3
  // --- Core Engine & Utilities ---
4
4
  export { Engine } from './engine.js';
@@ -7,7 +7,7 @@ import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
7
7
  import { HumanMessage, SystemMessage } from "@langchain/core/messages";
8
8
  import {searchData, patchData, deleteData, insertData} from "./data.js";
9
9
  import { getDataAsString } from "../data.js";
10
- import i18n from "../../../data-primals-engine/src/i18n.js";
10
+ import i18n from "data-primals-engine/i18n";
11
11
  import {generateLimiter} from "./user.js";
12
12
  import rateLimit from "express-rate-limit";
13
13
 
@@ -20,11 +20,17 @@ export const assistantGlobalLimiter = rateLimit({
20
20
  legacyHeaders: false, // Désactive les anciens en-têtes `X-RateLimit-*`
21
21
  message: { success: false, message: "Trop de requêtes globales envoyées à l'assistant. Veuillez réessayer plus tard." },
22
22
  skip: (req) => {
23
- return !!req.body?.confirmedAction;
23
+ return !!req.fields?.confirmedAction;
24
24
  }
25
25
  });
26
26
 
27
- const createSystemPrompt = (modelDefs, conditionBuilderExample) =>`
27
+ const createSystemPrompt = (modelDefs) => {
28
+ const cond1 = JSON.stringify({ "model": "event", "sort": "startDate:ASC", "limit": 0, "filter": { "$and": [{ "$nin": ["$tags", "festival"] }, { "$lt": ["$endDate", "$$NOW"] }] }}, null, 2);
29
+ const cond2 = JSON.stringify({ "model": "contact", "sort": "legalName:ASC", "limit": 5,"filter": {"$ne": [{ "$type": "$legalName"}, "missing"] }}, null, 2);
30
+ const cond3 = JSON.stringify({ "model": "order", "sort": "_id:DESC", "limit": 10,"filter": {"lang": { "$find": [{ "$eq": ["$$this.code", "fr"] }] } }}, null, 2);
31
+ const cond4 = JSON.stringify({ "model": "order", "sort": "updatedAt:DESC", "limit": 0,"filter": {"user": { "$find": { "roles": { "$find": [{ "$in": ["$$this.name", ["admin", "moderator"]] }] } }}}}, null, 2);
32
+
33
+ return `
28
34
  Tu es "Prior", un assistant expert en analyse de données pour la plateforme data.primals.net.
29
35
  Ta mission est d'aider l'utilisateur en répondant à ses questions sur ses données.
30
36
 
@@ -55,8 +61,11 @@ Tu as accès aux outils suivants. Tu ne dois utiliser QUE ces outils.
55
61
  5. **displayMessage**: Pour répondre directement à l'utilisateur. N'utilise cette action QUE lorsque tu as toutes les informations nécessaires pour formuler une réponse finale.
56
62
  - Utilisation: \`{ "action": "displayMessage", "params": { "message": "Ta réponse textuelle." } }\`
57
63
 
58
- La spécification suivante peut t'aider à construire les filtres) est la suivante :
59
- utilise une chaine de caracteres lorsque le type de champ est : code, string, string_t , password, url, phone, email, richtext
64
+ La spécification, pour t'aider à construire les filtres, est la suivante :
65
+ utilise une chaine de caractère convertible en ObjectId (mongodb) lorsque le nom du champ est _id
66
+ utilise une chaine de caracteres lorsque le type de champ est : string, string_t , password, url, phone, email, richtext
67
+ utilise un $FILTER en retour si le type de champ est code et language='json' et conditionBuilder=true
68
+ utilise une chaine si c'est un type de champ code par défaut.
60
69
  utilise une structure { "iso2langcode":"content..." } pour le champ multi-traductions richtext_t
61
70
  utilise un booléen pour : boolean
62
71
  utilise un nombre pour number
@@ -73,12 +82,26 @@ PROCESSUS DE RAISONNEMENT:
73
82
  2. Tu analyses la question et choisis l'outil approprié.
74
83
  3. Tu réponds IMMÉDIATEMENT avec l'objet JSON correspondant SANS COMMENTAIRE.
75
84
 
85
+ Chacune de tes réponses doit être une étape en soi, ou au plus près d'une étape.
86
+
76
87
  CONTEXTE ACTUEL:
77
88
  - L'utilisateur a accès aux modèles de données suivants et ne peut utiliser les filtres que sur les champs associés:
78
89
  ${modelDefs.map(m => ` - Modèle "${m.name}":\n Champs: ${JSON.stringify(m.fields.map(f => ({ name: f.name, type: f.type, hint: f.hint })), null, 2)}`).join('\n')}
79
- - Le format pour les filtres ($FILTER) est : ${conditionBuilderExample}
80
90
  - Le format de $DATA est { modelFieldName: "value", otherModelFieldName: { subObj : true } }
81
-
91
+ - Exemples de filtres $FILTER utilisables (ils sont compatibles MongoDB) :
92
+ Filtre utilisable : { $eq: ["$status", 500 ] }
93
+ Filtre incorrect : { "status": 500 }
94
+ ==
95
+
96
+ Exemples de "params" :
97
+ Je voudrais les événements non terminés, qui ne sont pas des festivals ou des salons :
98
+ \`${cond1}\`
99
+ Donne moi les 5 nouvelles entreprises
100
+ \`${cond2}\`
101
+ Je veux les 10 dernières traductions ajoutées dans la langue française.
102
+ \`${cond3}\`
103
+ Je veux les commandes qui ont été faites par un admin ou un modérateur
104
+ \`${cond4}\`
82
105
 
83
106
  Règles ABSOLUES:
84
107
  - UNE SEULE COMMANDE JSON PAR RÉPONSE
@@ -97,13 +120,19 @@ json
97
120
  \`\`\`
98
121
 
99
122
  Exemple de réponse INCORRECTE (INTERDIT) :
100
- "Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."`;
123
+ "Je vais d'abord rechercher ces requêtes. Voici la commande que je vais exécuter..."
124
+ `;
125
+ }
101
126
 
102
127
 
103
128
  /**
104
129
  * Gère la requête de chat, soit en exécutant une action confirmée,
105
130
  * soit en lançant la boucle de raisonnement de l'IA.
106
131
  */
132
+ /**
133
+ * Gère la requête de chat, soit en exécutant une action confirmée,
134
+ * soit en lanant la boucle de raisonnement de l'IA.
135
+ */
107
136
  async function handleChatRequest(message, history, provider, context, user, confirmedAction) {
108
137
 
109
138
  // --- GESTION D'UNE ACTION CONFIRMÉE ---
@@ -152,8 +181,7 @@ async function handleChatRequest(message, history, provider, context, user, conf
152
181
  const relevantModelNames = new Set([modelName, 'alert', 'kpi', "dashboard", "request", "translation"]);
153
182
  const modelDefs = allModels.filter(m => relevantModelNames.has(m.name));
154
183
 
155
- const conditionBuilderExample = JSON.stringify({ "$and": [{ "$eq": ["$fieldName", "value"] }] }, null, 2);
156
- const systemPrompt = createSystemPrompt(modelDefs, conditionBuilderExample);
184
+ const systemPrompt = createSystemPrompt(modelDefs);
157
185
 
158
186
  const conversationHistory = history
159
187
  .filter(msg => !(msg.from === 'bot' && msg.text.startsWith(i18n.t('assistant.welcome'))))
@@ -207,21 +235,27 @@ async function handleChatRequest(message, history, provider, context, user, conf
207
235
 
208
236
  return { success: true, displayMessage: resultString };
209
237
  }
210
-
211
238
  case 'displayMessage':
212
239
  return { success: true, displayMessage: parsedResponse.params.message };
213
-
214
240
  case 'code':
215
- return { success: true, displayMessage: "```json\n" + JSON.stringify(parsedResponse.params.message, null, 2) + "\n```" };
216
-
241
+ return { success: true, displayMessage: "Voici le code : " + parsedResponse.params.code };
217
242
  case 'post':
218
243
  case 'update':
219
- case 'delete':
244
+ case 'delete': {
245
+ const { model, filter, data } = parsedResponse.params;
246
+
247
+ // Un message générique. Les détails seront affichés par le front-end.
248
+ const confirmationMessage = i18n.t('assistant.confirmActionPrompt', "Veuillez confirmer l'action suivante :");
249
+
220
250
  return {
221
251
  success: true,
222
- displayMessage: i18n.t('assistant.confirmAction', `Je vais exécuter : ${parsedResponse.action}. Confirmez-vous ?`),
223
- confirmationRequest: parsedResponse
252
+ model,
253
+ filter,
254
+ data,
255
+ displayMessage: confirmationMessage, // Message détaillé pour l'utilisateur
256
+ confirmationRequest: parsedResponse // Action complète pour l'exécution
224
257
  };
258
+ }
225
259
 
226
260
  default:
227
261
  return {
@@ -234,7 +268,6 @@ async function handleChatRequest(message, history, provider, context, user, conf
234
268
  logger.warn("[Assistant] La boucle a atteint le nombre maximum de tours sans réponse.");
235
269
  return { success: true, displayMessage: i18n.t('assistant.loopTimeout', "Désolé, je n'ai pas réussi à terminer ma pensée. Pouvez-vous reformuler votre demande ?"), actions: [] };
236
270
  }
237
-
238
271
  /**
239
272
  * Exécute une action de modification (post, update, delete) après confirmation de l'utilisateur.
240
273
  * @param {string} action - L'action à exécuter ('post', 'update', 'delete').
@@ -266,7 +299,7 @@ export async function onInit(engine) {
266
299
 
267
300
  engine.post('/api/assistant/chat', [middlewareAuthenticator, userInitiator, assistantGlobalLimiter, generateLimiter], async (req, res) => {
268
301
  // On récupère TOUTES les propriétés du body, y compris l'action confirmée
269
- const {message, history, provider, context, confirmedAction} = req.body;
302
+ const {message, history, provider, context, confirmedAction} = req.fields;
270
303
 
271
304
  // La validation ne s'applique que s'il n'y a pas d'action confirmée
272
305
  if (!confirmedAction) {
@@ -9,7 +9,7 @@ import {Logger} from "../gameObject.js";
9
9
  import {middlewareAuthenticator, myFreePremiumAnonymousLimiter, userInitiator} from "./user.js";
10
10
  import {awsDefaultConfig, maxBytesPerSecondThrottleData} from "../constants.js";
11
11
  import crypto from "node:crypto";
12
- import i18n from "../../../data-primals-engine/src/i18n.js";
12
+ import i18n from "data-primals-engine/i18n";
13
13
  import {sendEmail} from "../email.js";
14
14
  import {throttleMiddleware} from "../middlewares/throttle.js";
15
15
 
@@ -179,7 +179,7 @@ export async function onInit(defaultEngine) {
179
179
 
180
180
  engine.post('/api/user/s3-config', [middlewareAuthenticator, userInitiator, myFreePremiumAnonymousLimiter], async (req, res) => {
181
181
  const user = req.me;
182
- const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.body;
182
+ const { bucketName, accessKeyId, secretAccessKey, region, pathPrefix } = req.fields;
183
183
 
184
184
  // Validation basique des entrées
185
185
  if (!bucketName || !accessKeyId || !region) {
@@ -201,7 +201,7 @@ export async function onInit(defaultEngine) {
201
201
  } else {
202
202
  // Si la clé secrète n'est pas fournie, on ne la modifie pas.
203
203
  // Si tu veux permettre de la supprimer, il faudrait une logique explicite.
204
- // Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.body.secretAccessKey est vide.
204
+ // Pour l'instant, on ne touche pas à s3Config.secretAccessKey si req.fields.secretAccessKey est vide.
205
205
  }
206
206
 
207
207
  const result = await engine.userProvider.updateUser(