data-primals-engine 1.7.2 → 1.7.3

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.
Files changed (78) hide show
  1. package/README.md +160 -160
  2. package/client/package-lock.json +8430 -8430
  3. package/client/src/APIInfo.jsx +1 -1
  4. package/client/src/App.jsx +2 -1
  5. package/client/src/App.scss +1635 -1626
  6. package/client/src/AssistantChat.jsx +1 -0
  7. package/client/src/CalendarView.jsx +1 -0
  8. package/client/src/ContentView.jsx +3 -3
  9. package/client/src/Dashboard.jsx +5 -2
  10. package/client/src/DashboardChart.jsx +1 -0
  11. package/client/src/DashboardFlexViewItem.jsx +1 -0
  12. package/client/src/DashboardHtmlViewItem.jsx +1 -0
  13. package/client/src/DashboardView.jsx +2 -0
  14. package/client/src/DataEditor.jsx +0 -1
  15. package/client/src/DataImporter.jsx +489 -468
  16. package/client/src/DataLayout.jsx +6 -3
  17. package/client/src/DataTable.jsx +4 -3
  18. package/client/src/Dialog.jsx +92 -90
  19. package/client/src/Dialog.scss +122 -116
  20. package/client/src/DisplayFlexNodeRenderer.jsx +1 -0
  21. package/client/src/FlexBuilderControls.jsx +1 -0
  22. package/client/src/FlexBuilderPreview.jsx +1 -1
  23. package/client/src/FlexNode.jsx +1 -1
  24. package/client/src/HistoryDialog.jsx +3 -2
  25. package/client/src/KPIWidget.jsx +1 -1
  26. package/client/src/KanbanView.jsx +1 -0
  27. package/client/src/ModelCreator.jsx +4 -0
  28. package/client/src/ModelList.jsx +1 -0
  29. package/client/src/PackGallery.jsx +5 -4
  30. package/client/src/RTETrans.jsx +1 -1
  31. package/client/src/RelationField.jsx +2 -0
  32. package/client/src/RelationSelectorWidget.jsx +2 -0
  33. package/client/src/RelationValue.jsx +1 -0
  34. package/client/src/RestoreDialog.jsx +1 -0
  35. package/client/src/WorkflowEditor.jsx +3 -0
  36. package/client/src/contexts/CommandContext.jsx +2 -1
  37. package/client/src/contexts/ModelContext.jsx +3 -3
  38. package/client/src/hooks/data.js +1 -0
  39. package/client/src/hooks/useValidation.js +1 -0
  40. package/client/src/translations.js +24 -24
  41. package/doc/AI-assistance.md +87 -63
  42. package/doc/Concepts.md +122 -0
  43. package/doc/Custom-Endpoints.md +31 -0
  44. package/doc/Event-system.md +13 -14
  45. package/doc/Home.md +33 -0
  46. package/doc/Modules.md +83 -0
  47. package/doc/Packs-gallery.md +8 -23
  48. package/doc/Workflows.md +32 -0
  49. package/doc/automation-workflows.md +141 -102
  50. package/doc/dashboards-kpis-charts.md +47 -49
  51. package/doc/data-management.md +126 -120
  52. package/doc/data-models.md +68 -75
  53. package/doc/roles-permissions.md +144 -43
  54. package/doc/sharding-replication.md +158 -0
  55. package/doc/users.md +54 -30
  56. package/package.json +1 -1
  57. package/server.js +37 -37
  58. package/src/constants.js +7 -8
  59. package/src/data.js +521 -520
  60. package/src/email.js +157 -154
  61. package/src/engine.js +117 -7
  62. package/src/gameObject.js +6 -0
  63. package/src/i18n.js +0 -1
  64. package/src/modules/auth-google/index.js +53 -50
  65. package/src/modules/bucket.js +346 -339
  66. package/src/modules/data/data.backup.js +400 -376
  67. package/src/modules/data/data.cluster.js +410 -42
  68. package/src/modules/data/data.operations.js +3666 -3635
  69. package/src/modules/data/data.replication.js +106 -64
  70. package/src/modules/data/data.routes.js +87 -64
  71. package/src/modules/data/data.scheduling.js +2 -1
  72. package/src/modules/file.js +248 -247
  73. package/src/modules/user.js +11 -1
  74. package/src/sso.js +91 -25
  75. package/test/cluster.test.js +221 -0
  76. package/test/core.test.js +0 -2
  77. package/test/replication.test.js +163 -0
  78. package/doc/core-concepts.md +0 -33
@@ -15,7 +15,7 @@ const RTETrans = ({ value, onChange, field }) => {
15
15
  params.append('model', 'lang');
16
16
  params.append("_user", getUserId(me));
17
17
  params.append("depth", '1');
18
- return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', body: JSON.stringify({}), headers: { "Content-Type": "application/json"}})
18
+ return fetch(`/api/data/search?${params.toString()}`, { credentials:"include", signal, method: 'POST', body: JSON.stringify({}), headers: { "Content-Type": "application/json"}})
19
19
  .then((res) => res.json())
20
20
  .then((data) => {
21
21
  setAvailableLangs(data.data || []);
@@ -49,6 +49,7 @@ const RelationField = ({ field, help, onFocus, onBlur, onChange, value = null })
49
49
 
50
50
  const response = await fetch(`/api/data/search?${params.toString()}`, {
51
51
  method: 'POST',
52
+ credentials:"include",
52
53
  headers: { 'Content-Type': 'application/json' },
53
54
  body: JSON.stringify({ filter: {} }) // Le filtre peut être vide quand on cherche par IDs
54
55
  });
@@ -126,6 +127,7 @@ const RelationField = ({ field, help, onFocus, onBlur, onChange, value = null })
126
127
  body: JSON.stringify({ filter: finalFilter }),
127
128
  method: 'POST',
128
129
  signal: signal,
130
+ credentials:"include",
129
131
  headers: { 'Content-Type': 'application/json' },
130
132
  })
131
133
  .then(e => e.json())
@@ -13,6 +13,7 @@ import { useAuthContext } from './contexts/AuthContext.jsx';
13
13
  const insertDataAPI = async (modelName, data) => {
14
14
  const response = await fetch(`/api/data`, {
15
15
  method: 'POST',
16
+ credentials:"include",
16
17
  headers: { 'Content-Type': 'application/json' },
17
18
  body: JSON.stringify({model: modelName,data})
18
19
  });
@@ -27,6 +28,7 @@ const searchDataAPI = async (modelName, filter) => {
27
28
  const params = new URLSearchParams({ model: modelName, depth: '1' });
28
29
  const response = await fetch(`/api/data/search?${params.toString()}`, {
29
30
  method: 'POST',
31
+ credentials:"include",
30
32
  headers: { 'Content-Type': 'application/json' },
31
33
  body: JSON.stringify({ filter })
32
34
  });
@@ -44,6 +44,7 @@ const RelationValue = ({ field, data, align }) => {
44
44
 
45
45
  return fetch(`/api/data/search?${params.toString()}`, {
46
46
  method: 'POST',
47
+ credentials:"include",
47
48
  headers: { 'Content-Type': 'application/json' },
48
49
  })
49
50
  .then(res => res.json())
@@ -42,6 +42,7 @@ function RestoreDialog() {
42
42
  try {
43
43
  const response = await fetch(apiUrl, {
44
44
  method: 'GET', // Ou 'POST' si l'API l'exige, mais GET semble plus probable ici
45
+ credentials:"include",
45
46
  headers: {
46
47
  'Accept': 'application/json',
47
48
  // Ajoute d'autres headers si nécessaire (ex: Authorization si requis)
@@ -84,6 +84,7 @@ const WorkflowEditor = ({ workflowId }) => {
84
84
  ['mainWorkflow', workflowId],
85
85
  () => fetch('/api/data/search?_user='+me.username, {
86
86
  method: 'POST',
87
+ credentials:"include",
87
88
  headers: { 'Content-Type': 'application/json' },
88
89
  body: JSON.stringify({
89
90
  model: 'workflow',
@@ -102,6 +103,7 @@ const WorkflowEditor = ({ workflowId }) => {
102
103
  ['workflowSteps', workflowId],
103
104
  () => fetch('/api/data/search?_user=' + me.username, {
104
105
  method: 'POST',
106
+ credentials:"include",
105
107
  headers: { 'Content-Type': 'application/json' },
106
108
  body: JSON.stringify({
107
109
  model: 'workflowStep',
@@ -122,6 +124,7 @@ const WorkflowEditor = ({ workflowId }) => {
122
124
  ['workflowActions', workflowId],
123
125
  () => fetch('/api/data/search?_user=' + me.username, {
124
126
  method: 'POST',
127
+ credentials:"include",
125
128
  headers: { 'Content-Type': 'application/json' },
126
129
  body: JSON.stringify({
127
130
  model: 'workflowAction',
@@ -116,7 +116,7 @@ export class InsertCommand {
116
116
  async undo() {
117
117
  // On garde une copie de l'item avant de le supprimer pour le redo.
118
118
  if (!this.insertedItem?._id) throw new Error("Cannot undo insert: item ID is missing.");
119
- const response = await fetch(`/api/data/${this.insertedItem._id}`, { method: 'DELETE' });
119
+ const response = await fetch(`/api/data/${this.insertedItem._id}`, { credentials:"include",method: 'DELETE' });
120
120
  const result = await response.json();
121
121
  if (!response.ok || !result.success) {
122
122
  throw new Error(result.error || 'Undo (delete) failed');
@@ -184,6 +184,7 @@ export class DeleteCommand {
184
184
 
185
185
  return fetch('/api/data?_user=system', { // On ajoute _user=system pour tracer l'origine de l'action
186
186
  method: 'POST',
187
+ credentials:"include",
187
188
  headers: { 'Content-Type': 'application/json' },
188
189
  body: JSON.stringify({ model: this.modelName, data: reinsertData }),
189
190
  }).then(res => res.json().then(data => {
@@ -45,7 +45,7 @@ export const ModelProvider = ({ children }) => {
45
45
  const { data: dataModels = [], isFetched } = useQuery(['api/models', me], () => {
46
46
  if(!me)
47
47
  return Promise.reject();
48
- return fetch(`/api/models?lang=${lang}&_user=${encodeURIComponent(getUserId(me))}`).then((res) => {
48
+ return fetch(`/api/models?lang=${lang}&_user=${encodeURIComponent(getUserId(me))}`, {credentials:"include"}).then((res) => {
49
49
  if( !res.ok ){
50
50
  return [];
51
51
  }
@@ -118,7 +118,7 @@ export const ModelProvider = ({ children }) => {
118
118
  if( rel){
119
119
  return Promise.resolve(rel);
120
120
  }
121
- return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', headers: { "Content-Type": "application/json"}})
121
+ return fetch(`/api/data/search?${params.toString()}`, { credentials:"include",signal, method: 'POST', headers: { "Content-Type": "application/json"}})
122
122
  .then((res) => res.json())
123
123
  .then((e) => ({
124
124
  count: e.count,
@@ -204,7 +204,7 @@ export const ModelProvider = ({ children }) => {
204
204
  const c = pagedFilterToMongoConds(pagedFilters, model)
205
205
  const filter= JSON.stringify({filter:{$and:c}});
206
206
 
207
- return fetch(`/api/data/search?${params.toString()}`, { signal, method: 'POST', body: filter, headers: { "Content-Type": "application/json"}})
207
+ return fetch(`/api/data/search?${params.toString()}`, { signal, credentials: "include", method: 'POST', body: filter, headers: { "Content-Type": "application/json"}})
208
208
  .then((res) => res.json())
209
209
  .then((e) => {
210
210
  return {
@@ -12,6 +12,7 @@ export const useData = (model, filter, options) => {
12
12
  return useQuery([options.queryKey || model, filter, me], () => {
13
13
  return fetch('/api/data/search?limit='+(options.limit||1)+'&lang='+(options.lang||lang)+'&_user=' + encodeURIComponent(getUserId(me)) + '&model='+model, {
14
14
  method: 'POST',
15
+ credentials:"include",
15
16
  body: JSON.stringify({filter}),
16
17
  headers: { "Content-Type": "application/json"}}
17
18
  )
@@ -13,6 +13,7 @@ import { useState, useCallback, useMemo } from 'react';
13
13
  */
14
14
  const validateFieldOnServer = async (api, payload) => {
15
15
  return fetch('/api/data/validate', { method: 'POST',
16
+ credentials:"include",
16
17
  headers: {
17
18
  'Content-Type': 'application/json'
18
19
  }
@@ -457,8 +457,8 @@ export const websiteTranslations = {
457
457
  "btns.backup": "Restaurer",
458
458
  "backup.restore.title": "Envoi de votre lien de restauration",
459
459
  "backup.restore.confirm": "Un lien pour restaurer votre sauvegarde vous sera envoyé à votre adresse e-mail. Ce lien expirera dans 30 minutes. Voulez-vous vraiment continuer?",
460
- "email.backup.restoreRequest.subject": "Votre demande de restauration de sauvegarde",
461
- "email.backup.restoreRequest.content": `<p>Vous avez demandé une restauration de sauvegarde. Veuillez choisir une option :</p>
460
+ "email_backup_restoreRequest_subject": "Votre demande de restauration de sauvegarde",
461
+ "email_backup_restoreRequest_content": `<p>Vous avez demandé une restauration de sauvegarde. Veuillez choisir une option :</p>
462
462
  <p><strong>ATTENTION : Toute restauration écrasera vos données actuelles (modèles et/ou données).</strong></p>
463
463
  <ul>
464
464
  <li><a href="https://{{host}}/backup/restore?token={{modelsToken}}&username={{user}}&type=full">Restauration Complète (Modèles ET Données)</a></li>
@@ -1570,8 +1570,8 @@ export const websiteTranslations = {
1570
1570
  "backup.restore.title": "Sending your restore link",
1571
1571
  "btns.backup": "Restore",
1572
1572
  "backup.restore.confirm": "A link to restore your backup will be sent to your email address. This link will expire in 30 minutes. Are you sure you want to proceed?",
1573
- "email.backup.restoreRequest.subject": "Your Backup restoration request",
1574
- "email.backup.restoreRequest.content": `<p>You have requested a backup restoration. Please choose an option:</p>
1573
+ "email_backup_restoreRequest_subject": "Your Backup restoration request",
1574
+ "email_backup_restoreRequest_content": `<p>You have requested a backup restoration. Please choose an option:</p>
1575
1575
  <p><strong>WARNING: Any restoration will overwrite your current data (models and/or data).</strong></p>
1576
1576
  <ul>
1577
1577
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Full Restoration (Models AND Data)</a></li>
@@ -3046,8 +3046,8 @@ export const websiteTranslations = {
3046
3046
  "btns.cancel": "Cancelar",
3047
3047
  "btns.backup": "Restaurar",
3048
3048
  "backup.restore.confirm": "Se enviará un enlace para restaurar su copia de seguridad a su correo electrónico. Este enlace caducará en 30 minutos. ¿Está seguro de que desea continuar?",
3049
- "email.backup.restoreRequest.subject": "Su solicitud de restauración de copia de seguridad",
3050
- "email.backup.restoreRequest.content": `<p>Has solicitado una restauración de copia de seguridad. Por favor, elige una opción:</p>
3049
+ "email_backup_restoreRequest_subject": "Su solicitud de restauración de copia de seguridad",
3050
+ "email_backup_restoreRequest_content": `<p>Has solicitado una restauración de copia de seguridad. Por favor, elige una opción:</p>
3051
3051
  <p><strong>ADVERTENCIA: Cualquier restauración sobrescribirá tus datos actuales (modelos y/o datos).</strong></p>
3052
3052
  <ul>
3053
3053
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Restauración completa (Modelos Y Datos)</a></li>
@@ -4528,8 +4528,8 @@ export const websiteTranslations = {
4528
4528
  "btns.cancel": "Cancelar",
4529
4529
  "btns.backup": "Restaurar",
4530
4530
  "backup.restore.confirm": "Será enviado um link para restaurar o seu backup para o seu endereço de e-mail. Este link expirará em 30 minutos. Tem a certeza de que pretende prosseguir?",
4531
- "email.backup.restoreRequest.subject": "O seu pedido de restauro de cópia de segurança",
4532
- "email.backup.restoreRequest.content": `<p>Solicitou a restauração de uma cópia de segurança. Por favor, escolha uma opção:</p>
4531
+ "email_backup_restoreRequest_subject": "O seu pedido de restauro de cópia de segurança",
4532
+ "email_backup_restoreRequest_content": `<p>Solicitou a restauração de uma cópia de segurança. Por favor, escolha uma opção:</p>
4533
4533
  <p><strong>ATENÇÃO: Qualquer restauração irá substituir os seus dados atuais (modelos e/ou dados).</strong></p>
4534
4534
  <ul>
4535
4535
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Restauração Completa (Modelos E Dados)</a></li>
@@ -5984,8 +5984,8 @@ export const websiteTranslations = {
5984
5984
  "btns.cancel": "Abbrechen",
5985
5985
  "btns.backup": "Wiederherstellen",
5986
5986
  "backup.restore.confirm": "Ein Link zur Wiederherstellung Ihres Backups wird an Ihre E-Mail-Adresse gesendet. Dieser Link läuft in 30 Minuten ab. Möchten Sie wirklich fortfahren?",
5987
- "email.backup.restoreRequest.subject": "Ihre Anfrage zur Wiederherstellung des Backups",
5988
- "email.backup.restoreRequest.content": `<p>Sie haben die Wiederherstellung eines Backups angefordert. Bitte wählen Sie eine Option:</p>
5987
+ "email_backup_restoreRequest_subject": "Ihre Anfrage zur Wiederherstellung des Backups",
5988
+ "email_backup_restoreRequest_content": `<p>Sie haben die Wiederherstellung eines Backups angefordert. Bitte wählen Sie eine Option:</p>
5989
5989
  <p><strong>ACHTUNG: Jede Wiederherstellung überschreibt Ihre aktuellen Daten (Modelle und/oder Daten).</strong></p>
5990
5990
  <ul>
5991
5991
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Vollständige Wiederherstellung (Modelle UND Daten)</a></li>
@@ -7463,8 +7463,8 @@ export const websiteTranslations = {
7463
7463
  "btns.cancel": "Annulla",
7464
7464
  "btns.backup": "Ripristina",
7465
7465
  "backup.restore.confirm": "Un link per ripristinare il backup verrà inviato al tuo indirizzo email. Questo link scadrà tra 30 minuti. Vuoi procedere?",
7466
- "email.backup.restoreRequest.subject": "La tua richiesta di ripristino del backup",
7467
- "email.backup.restoreRequest.content": `<p>Hai richiesto il ripristino di un backup. Scegli un'opzione:</p>
7466
+ "email_backup_restoreRequest_subject": "La tua richiesta di ripristino del backup",
7467
+ "email_backup_restoreRequest_content": `<p>Hai richiesto il ripristino di un backup. Scegli un'opzione:</p>
7468
7468
  <p><strong>ATTENZIONE: Qualsiasi ripristino sovrascriverà i tuoi dati attuali (modelli e/o dati).</strong></p>
7469
7469
  <ul>
7470
7470
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Ripristino Completo (Modelli E Dati)</a></li>
@@ -8937,8 +8937,8 @@ export const websiteTranslations = {
8937
8937
  "btns.cancel": "Zrušit",
8938
8938
  "btns.backup": "Obnovit",
8939
8939
  "backup.restore.confirm": "Na vaši e-mailovou adresu bude odeslán odkaz pro obnovení zálohy. Platnost tohoto odkazu vyprší za 30 minut. Opravdu chcete pokračovat?",
8940
- "email.backup.restoreRequest.subject": "Váš požadavek na obnovení zálohy",
8941
- "email.backup.restoreRequest.content": `<p>Požádali jste o obnovení ze zálohy. Vyberte prosím možnost:</p>
8940
+ "email_backup_restoreRequest_subject": "Váš požadavek na obnovení zálohy",
8941
+ "email_backup_restoreRequest_content": `<p>Požádali jste o obnovení ze zálohy. Vyberte prosím možnost:</p>
8942
8942
  <p><strong>VAROVÁNÍ: Jakákoli obnova přepíše vaše aktuální data (modely a/nebo data).</strong></p>
8943
8943
  <ul>
8944
8944
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Kompletní obnova (Modely I Data)</a></li>
@@ -10409,8 +10409,8 @@ export const websiteTranslations = {
10409
10409
  "btns.cancel": "Отмена",
10410
10410
  "btns.backup": "Восстановить",
10411
10411
  "backup.restore.confirm": "Ссылка для восстановления резервной копии будет отправлена ​​на ваш адрес электронной почты. Срок действия этой ссылки истекает через 30 минут. Вы уверены, что хотите продолжить?",
10412
- "email.backup.restoreRequest.subject": "Ваш запрос на восстановление резервной копии",
10413
- "email.backup.restoreRequest.content": `<p>Вы запросили восстановление из резервной копии. Пожалуйста, выберите вариант:</p>
10412
+ "email_backup_restoreRequest_subject": "Ваш запрос на восстановление резервной копии",
10413
+ "email_backup_restoreRequest_content": `<p>Вы запросили восстановление из резервной копии. Пожалуйста, выберите вариант:</p>
10414
10414
  <p><strong>ВНИМАНИЕ: Любое восстановление перезапишет ваши текущие данные (модели и/или данные).</strong></p>
10415
10415
  <ul>
10416
10416
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Полное восстановление (Модели И Данные)</a></li>
@@ -11900,8 +11900,8 @@ export const websiteTranslations = {
11900
11900
  "btns.cancel": "إلغاء",
11901
11901
  "btns.backup": "استعادة",
11902
11902
  "backup.restore.confirm": "سيتم إرسال رابط لاستعادة النسخة الاحتياطية إلى بريدك الإلكتروني. سينتهي هذا الرابط خلال 30 دقيقة. هل أنت متأكد من رغبتك في المتابعة؟",
11903
- "email.backup.restoreRequest.subject": "طلب استعادة النسخة الاحتياطية الخاص بك",
11904
- "email.backup.restoreRequest.content": `<p>لقد طلبت استعادة نسخة احتياطية. يرجى اختيار خيار:</p>
11903
+ "email_backup_restoreRequest_subject": "طلب استعادة النسخة الاحتياطية الخاص بك",
11904
+ "email_backup_restoreRequest_content": `<p>لقد طلبت استعادة نسخة احتياطية. يرجى اختيار خيار:</p>
11905
11905
  <p><strong>تحذير: أي عملية استعادة ستستبدل بياناتك الحالية (النماذج و/أو البيانات).</strong></p>
11906
11906
  <ul>
11907
11907
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">استعادة كاملة (النماذج والبيانات)</a></li>
@@ -13380,8 +13380,8 @@ export const websiteTranslations = {
13380
13380
  "btns.cancel": "Avbryt",
13381
13381
  "btns.backup": "Återställ",
13382
13382
  "backup.restore.confirm": "En länk för att återställa din säkerhetskopia kommer att skickas till din e-postadress. Den här länken upphör att gälla om 30 minuter. Är du säker på att du vill fortsätta?",
13383
- "email.backup.restoreRequest.subject": "Din begäran om återställning av säkerhetskopia",
13384
- "email.backup.restoreRequest.content": `<p>Du har begärt en återställning från backup. Vänligen välj ett alternativ:</p>
13383
+ "email_backup_restoreRequest_subject": "Din begäran om återställning av säkerhetskopia",
13384
+ "email_backup_restoreRequest_content": `<p>Du har begärt en återställning från backup. Vänligen välj ett alternativ:</p>
13385
13385
  <p><strong>OBS: All återställning kommer att skriva över din nuvarande data (modeller och/eller data).</strong></p>
13386
13386
  <ul>
13387
13387
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Fullständig återställning (Modeller OCH Data)</a></li>
@@ -14854,8 +14854,8 @@ export const websiteTranslations = {
14854
14854
  "btns.cancel": "Ακύρωση",
14855
14855
  "btns.backup": "Επαναφορά",
14856
14856
  "backup.restore.confirm": "Ένας σύνδεσμος για την επαναφορά του αντιγράφου ασφαλείας θα σταλεί στη διεύθυνση email σας. Αυτός ο σύνδεσμος θα λήξει σε 30 λεπτά. Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
14857
- "email.backup.restoreRequest.subject": "Το αίτημα επαναφοράς αντιγράφων ασφαλείας",
14858
- "email.backup.restoreRequest.content": `<p>Έχετε αιτηθεί επαναφορά από αντίγραφο ασφαλείας. Παρακαλώ επιλέξτε μια επιλογή:</p>
14857
+ "email_backup_restoreRequest_subject": "Το αίτημα επαναφοράς αντιγράφων ασφαλείας",
14858
+ "email_backup_restoreRequest_content": `<p>Έχετε αιτηθεί επαναφορά από αντίγραφο ασφαλείας. Παρακαλώ επιλέξτε μια επιλογή:</p>
14859
14859
  <p><strong>ΠΡΟΣΟΧΗ: Οποιαδήποτε επαναφορά θα αντικαταστήσει τα τρέχοντα δεδομένα σας (μοντέλα και/ή δεδομένα).</strong></p>
14860
14860
  <ul>
14861
14861
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">Πλήρης Επαναφορά (Μοντέλα ΚΑΙ Δεδομένα)</a></li>
@@ -16283,8 +16283,8 @@ export const websiteTranslations = {
16283
16283
  "btns.backup": "بازیابی",
16284
16284
  "backup.restore.title": "ارسال لینک بازیابی شما",
16285
16285
  "backup.restore.confirm": "لینکی برای بازیابی پشتیبان‌گیری شما به آدرس ایمیل شما ارسال خواهد شد. این لینک پس از 30 دقیقه منقضی می‌شود. آیا واقعاً می‌خواهید ادامه دهید؟",
16286
- "email.backup.restoreRequest.subject": "درخواست بازیابی پشتیبان‌گیری شما",
16287
- "email.backup.restoreRequest.content": `<p>درخواست بازیابی نسخه پشتیبان را داده‌اید. لطفاً یک گزینه انتخاب کنید:</p>
16286
+ "email_backup_restoreRequest_subject": "درخواست بازیابی پشتیبان‌گیری شما",
16287
+ "email_backup_restoreRequest_content": `<p>درخواست بازیابی نسخه پشتیبان را داده‌اید. لطفاً یک گزینه انتخاب کنید:</p>
16288
16288
  <p><strong>هشدار: هرگونه بازیابی، داده‌های فعلی شما (مدل‌ها و/یا داده‌ها) را بازنویسی خواهد کرد.</strong></p>
16289
16289
  <ul>
16290
16290
  <li><a href="https://{{host}}/backup/restore?token={{fullToken}}&username={{user}}&type=full">بازیابی کامل (هم مدل‌ها و هم داده‌ها)</a></li>
@@ -1,93 +1,117 @@
1
1
  # AI Assistance: Leverage Artificial Intelligence
2
2
 
3
- `data-primals-engine` includes a powerful, built-in AI assistant named **Prior**. This assistant is designed to understand natural language queries, allowing you to interact with your data, generate insights, and perform actions without writing complex code or API requests.
3
+ The `data-primals-engine` integrates powerful generative AI capabilities directly into its core, allowing you to automate content creation, analyze data, and enrich your business processes.
4
4
 
5
5
  ## Enabling the AI Assistant
6
6
 
7
- To use the AI assistant, you must first provide API keys for one or more supported AI providers. The engine natively supports providers like **OpenAI, Google (Gemini), DeepSeek, and Anthropic**.
7
+ To use the AI features, you must first provide API keys for one or more supported AI providers. The engine natively supports providers like **OpenAI, Google (Gemini), DeepSeek, and Anthropic**.
8
8
 
9
9
  You can configure your keys in two ways:
10
10
 
11
11
  1. **Environment Variables**: Set the appropriate variable in your `.env` file. This is the recommended approach for production.
12
- ```env
13
- # Choose one or more providers
14
- OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
15
- GOOGLE_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxx
16
- DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
17
- ```
18
12
 
19
- 2. **`env` Model**: For user-specific keys, you can store them in the `env` model within the application. The assistant will automatically look for a key belonging to the current user.
20
-
21
- Once a key is configured, the AI assistant chat interface becomes available in the UI.
22
-
23
- ## Core Capabilities
24
-
25
- The assistant, "Prior," can perform a wide range of actions based on your natural language commands.
26
-
27
- ### 1. Data Querying and Visualization
28
-
29
- You can ask the assistant to find, filter, and display your data. It can present the results in various formats.
30
-
31
- - **Simple Search**:
32
- > "Show me the latest 5 orders from French customers."
33
-
34
- - **Chart Generation**: Create charts on the fly.
35
- > "Create a bar chart of user sign-ups per month for the last year."
36
-
37
- - **Custom HTML Views**: Generate sophisticated, styled views of your data using Handlebars templates.
38
- > "Build a dashboard of my active projects with their status and due dates, using a modern dark theme."
39
-
40
- ### 2. Data Modification (with Confirmation)
13
+ ```env
14
+ # Choose one or more providers
15
+ OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
16
+ GOOGLE_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxx
17
+ DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
18
+ ANTHROPIC_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
19
+ ```
41
20
 
42
- The assistant can create, update, or delete data, but it will **always ask for your confirmation** before executing a modifying action. This provides a critical safety layer.
21
+ 2. **`env` Model**: For user-specific keys, you can store them in the `env` model within the application. The assistant will automatically look for a key belonging to the current user.
43
22
 
44
- - **Create Data**:
45
- > "Create a new task to 'Follow up with Client X' due tomorrow."
23
+ Once a key is configured, AI capabilities become available, most notably within the automation engine.
46
24
 
47
- - **Update Data**:
48
- > "Update the status of all tickets in the 'Support' category to 'resolved'."
25
+ ## AI in Workflows: The `GenerateAIContent` Action
49
26
 
50
- - **Delete Data**:
51
- > "Delete all draft products created before last month."
27
+ The most powerful way to use AI in `data-primals-engine` is by integrating it into your **Workflows**. The `GenerateAIContent` action type allows you to call an AI model as a step in any automated process.
52
28
 
53
- When you issue such a command, the assistant will respond with a summary of the action it intends to perform and a "Confirm" button. The action is only executed after you click it.
29
+ This is perfect for tasks like:
30
+ - Summarizing a new support ticket.
31
+ - Generating a product description when a new product is added.
32
+ - Classifying incoming data based on its content.
33
+ - Translating text.
54
34
 
55
- ### 3. Answering Questions
35
+ ### Example: Automatically Generating a Product Description
56
36
 
57
- The assistant can answer questions about the data it has access to.
37
+ Let's create a complete workflow that automatically generates an SEO description whenever a new product is added without one.
58
38
 
59
- > "What was our total revenue in the last quarter?"
39
+ This workflow can be created programmatically by making the following API calls from your JavaScript code.
60
40
 
61
- ## How It Works: The Reasoning Loop
62
41
 
63
- When you send a message, the assistant follows a strict reasoning process:
42
+ #### 1. The `workflow`
64
43
 
65
- 1. **Analyze Intent**: It first analyzes your request to understand what you want to do.
66
- 2. **Search Models (Internal Step)**: Its first action is always to call the `search_models` tool internally. This gives it the exact structure, field names, and types for the relevant data models. This step is crucial for preventing errors and "hallucinations."
67
- 3. **Formulate Action**: Based on your intent and the model structures it found, it formulates a final action, such as `search`, `generateChart`, or a `post` request.
68
- 4. **Execute or Confirm**:
69
- - If it's a read-only action (like `search` or `generateChart`), it executes it and displays the result.
70
- - If it's a write action (like `post` or `delete`), it presents the action to you for confirmation.
44
+ First, we define the main workflow. This document acts as a container for the steps and logic.
71
45
 
72
- ## AI in Workflows: The `GenerateAIContent` Action
46
+ ```javascript
47
+ await insertData("workflow", {
48
+ "name": "Generate Product SEO Description",
49
+ "description": "When a new product is created without an SEO description, this workflow uses AI to generate one based on the product's name.",
50
+ "startStep": { "$find": { "name": "Generate and Update Description" } }
51
+ });
52
+ ```
73
53
 
74
- Beyond the chat interface, you can leverage AI directly within your automation **Workflows**. The `GenerateAIContent` action allows you to call an AI model as a step in a workflow.
54
+ #### 2. The `workflowAction` documents
75
55
 
76
- This is perfect for tasks like:
77
- - Summarizing a new support ticket.
78
- - Generating a product description when a new product is added.
79
- - Classifying incoming data based on its content.
80
- - Translating text.
56
+ Next, we define the individual actions that will be performed. We need two actions: one to generate the content with AI, and another to update the product with that content. We use `insertData` to create both in a single call.
81
57
 
82
- **Example `workflowAction`:**
83
- ```json
84
- {
85
- "name": "Summarize Ticket",
58
+ ```javascript
59
+ await insertData("workflowAction", [
60
+ {
61
+ "name": "Generate SEO Content",
86
62
  "type": "GenerateAIContent",
87
63
  "aiProvider": "OpenAI",
88
- "aiModel": "gpt-4-turbo",
89
- "prompt": "Summarize the following support ticket in one sentence: {triggerData.description}"
90
- }
64
+ "aiModel": "gpt-4o-mini",
65
+ "prompt": "Write a short, engaging, and SEO-optimized product description (30-40 words) for the following product: '{triggerData.name}'. Return only the description text, without any introductory or concluding phrases."
66
+ },
67
+ {
68
+ "name": "Update Product with AI Content",
69
+ "type": "UpdateData",
70
+ "targetModel": "product",
71
+ "targetSelector": { "_id": "{triggerData._id}" },
72
+ "fieldsToUpdate": { "seoDescription": "{context.aiContent}" }
73
+ }
74
+ ]);
75
+ ```
76
+ > **Note**: The `targetSelector` and `fieldsToUpdate` use placeholders. `{triggerData._id}` refers to the ID of the product that started the workflow, and `{context.aiContent}` refers to the text generated by the previous AI action.
77
+
78
+ #### 3. The `workflowStep`
79
+
80
+ Now, we create a step that groups our actions together. A workflow can have multiple steps that execute in sequence. For this simple case, we only need one, which we create with `insertData`.
81
+
82
+ ```javascript
83
+ await insertData("workflowStep", {
84
+ "name": "Generate and Update Description",
85
+ "workflow": { "$find": { "name": "Generate Product SEO Description" } },
86
+ "actions": [
87
+ { "$find": { "name": "Generate SEO Content" } },
88
+ { "$find": { "name": "Update Product with AI Content" } }
89
+ ],
90
+ "isTerminal": true
91
+ });
91
92
  ```
92
93
 
94
+ #### 4. The `workflowTrigger`
95
+
96
+ Finally, we define the trigger that will start the workflow. This trigger will listen for new documents being added (`DataAdded`) to the `product` model. It also includes a `dataFilter` to ensure it only runs if the `seoDescription` field is empty. We create it with `insertData`.
97
+
98
+ ```javascript
99
+ await insertData("workflowTrigger", {
100
+ "name": "On New Product Added without SEO Description",
101
+ "workflow": { "$find": { "name": "Generate Product SEO Description" } },
102
+ "type": "manual",
103
+ "onEvent": "DataAdded",
104
+ "targetModel": "product",
105
+ "dataFilter": {
106
+ "$or": [
107
+ { "seoDescription": { "$exists": false } },
108
+ { "seoDescription": "" }
109
+ ]
110
+ },
111
+ "isActive": true
112
+ });
113
+ ```
114
+
115
+ With these four API calls, your automated AI content generation workflow is now active. Any new product created without an SEO description will automatically have one generated and saved.
116
+
93
117
  This integration of AI both as an interactive assistant and as an automation component makes `data-primals-engine` a powerful platform for building intelligent applications.