data-primals-engine 1.7.1 → 1.7.2

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 (36) hide show
  1. package/README.md +160 -160
  2. package/client/package-lock.json +484 -175
  3. package/client/package.json +7 -4
  4. package/client/src/AssistantChat.jsx +1 -3
  5. package/client/src/DataLayout.jsx +19 -20
  6. package/client/src/DataTable.jsx +2 -2
  7. package/client/src/DocumentationPageLayout.scss +1 -1
  8. package/client/src/ViewSwitcher.jsx +1 -1
  9. package/client/vite.config.js +31 -30
  10. package/package.json +27 -17
  11. package/src/ai.jobs.js +135 -0
  12. package/src/constants.js +561 -545
  13. package/src/data.js +2 -0
  14. package/src/engine.js +50 -42
  15. package/src/modules/assistant/assistant.js +782 -763
  16. package/src/modules/assistant/constants.js +23 -16
  17. package/src/modules/assistant/providers.js +77 -37
  18. package/src/modules/bucket.js +4 -0
  19. package/src/modules/data/data.cluster.js +191 -0
  20. package/src/modules/data/data.core.js +11 -8
  21. package/src/modules/data/data.js +311 -311
  22. package/src/modules/data/data.operations.js +186 -106
  23. package/src/modules/data/data.relations.js +1 -0
  24. package/src/modules/data/data.replication.js +83 -0
  25. package/src/modules/data/data.routes.js +2183 -1879
  26. package/src/modules/mongodb.js +76 -73
  27. package/src/modules/user.js +7 -1
  28. package/src/modules/worker-script-runner.js +97 -0
  29. package/src/modules/workflow.js +177 -52
  30. package/src/packs.js +5701 -5701
  31. package/src/providers.js +298 -297
  32. package/test/assistant.test.js +207 -206
  33. package/test/data.integration.test.js +1425 -1416
  34. package/test/import_export.integration.test.js +210 -210
  35. package/test/workflow.actions.integration.test.js +487 -475
  36. package/test/workflow.integration.test.js +332 -329
@@ -13,6 +13,7 @@
13
13
  "preview": "vite preview"
14
14
  },
15
15
  "resolutions": {
16
+ "markdown-it": ">=14.1.1",
16
17
  "immutable": ">=5.1.5",
17
18
  "body-parser": ">=2.2.1",
18
19
  "js-yaml": ">=4.1.1",
@@ -31,7 +32,6 @@
31
32
  "react-cookie": "^8.0.1",
32
33
  "react-ga": "^3.3.1",
33
34
  "react-helmet": "^6.1.0",
34
- "react-icons": "^5.5.0",
35
35
  "react-international-phone": "^4.6.0",
36
36
  "react-query": "^3.39.3",
37
37
  "react-router-dom": "^7.8.1",
@@ -63,14 +63,17 @@
63
63
  "lowlight": "^3.3.0",
64
64
  "react": "^18.3.1",
65
65
  "reactflow": "^11.0.0-next.0",
66
+ "react-icons": "^5.5.0",
66
67
  "react-big-calendar": "^1.15.0",
67
68
  "react-color": "^2.19.3",
68
69
  "react-dom": "^18.3.1",
69
70
  "react-leaflet": "^4.2.1",
70
- "react-router": "7.12.0",
71
+ "react-router": ">=7.15.1",
71
72
  "react-switch": "^7.1.0",
72
73
  "uniqid": "^5.4.0",
73
- "yet-another-react-lightbox": "^3.25.0"
74
+ "yet-another-react-lightbox": "^3.25.0",
75
+ "flatted": ">=3.4.2",
76
+ "linkify-it": ">=5.0.1"
74
77
  },
75
78
  "devDependencies": {
76
79
  "@eslint/js": "^9.33.0",
@@ -84,6 +87,6 @@
84
87
  "globals": "^15.15.0",
85
88
  "process": "^0.11.10",
86
89
  "sass-embedded": "^1.90.0",
87
- "vite": "8.0.9"
90
+ "vite": ">=6.4.3"
88
91
  }
89
92
  }
@@ -1,5 +1,3 @@
1
- // client/src/components/AssistantChat.jsx
2
-
3
1
  import React, { useState, useMemo, useEffect, useRef } from 'react';
4
2
  import { FaRobot, FaPaperPlane, FaTimes, FaExpand, FaCompress, FaPlus } from 'react-icons/fa';
5
3
  import './AssistantChat.scss';
@@ -13,7 +11,7 @@ import FlexViewCard from "./FlexViewCard.jsx";
13
11
  import HtmlViewCard from "./HtmlViewCard.jsx";
14
12
  import {useUI} from "./contexts/UIContext.jsx";
15
13
  import Button from "./Button.jsx";
16
- import {getUserHash, getUserId} from "../../src/data.js";
14
+ import {getUserHash} from "../../src/data.js";
17
15
  import {useAuthContext} from "./contexts/AuthContext.jsx";
18
16
  import {useNavigate} from "react-router-dom";
19
17
  import {DataTable} from "./DataTable.jsx";
@@ -60,7 +60,6 @@ const NotConfiguredPlaceholder = ({ type, onConfigure }) => (
60
60
 
61
61
  const WorkflowSelectorModal = ({ onClose, onSelectWorkflow }) => {
62
62
  const { t } = useTranslation();
63
- const { searchData } = useModelContext();
64
63
  const { me } = useAuthContext();
65
64
  const { data: activeWorkflows, isLoading } = useQuery(
66
65
  'activeWorkflowsList',
@@ -96,7 +95,7 @@ const WorkflowSelectorModal = ({ onClose, onSelectWorkflow }) => {
96
95
  );
97
96
  };
98
97
 
99
- function DataLayout({refreshUI}) {
98
+ function DataLayout({refreshUI}, ref) {
100
99
  const [ searchParams, setSearchParams ] = useSearchParams();
101
100
  const [viewSettings, setViewSettings] = useLocalStorage('viewSettings', {});
102
101
 
@@ -147,6 +146,7 @@ function DataLayout({refreshUI}) {
147
146
 
148
147
  const mainPartRef = useRef();
149
148
  const modelCreatorRef = useRef();
149
+ const dataEditorRef = useRef(null);
150
150
 
151
151
  const [tutorialDialogVisible, setTutorialDialogVisible] = useState(false);
152
152
  const [importModalVisible, setImportModalVisible] = useState(false);
@@ -155,7 +155,6 @@ function DataLayout({refreshUI}) {
155
155
  const [showAPIInfo, setAPIInfoVisible] = useState(false);
156
156
  const nav = useNavigate();
157
157
  const mod = searchParams.get('model');
158
- const loc = useLocation();
159
158
 
160
159
 
161
160
  // --- AJOUT : Récupérer le paramètre de l'URL pour l'édition de workflow ---
@@ -300,18 +299,24 @@ function DataLayout({refreshUI}) {
300
299
  setFilterValues={setFilterValues}
301
300
  model={selectedModel}
302
301
  onAddData={(model) => {
303
- mainPartRef.current.scrollIntoView({behavior: "smooth"});
304
302
  handleAddData(model);
303
+ setTimeout(() => {
304
+ dataEditorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
305
+ }, 100);
305
306
  }}
306
307
  onDuplicateData={(data) => {
307
- mainPartRef.current.scrollIntoView({behavior: "smooth"});
308
308
  handleAddData(selectedModel, data);
309
+ setTimeout(() => {
310
+ dataEditorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
311
+ }, 100);
309
312
  }}
310
313
  onEdit={(item) => {
311
- mainPartRef.current.scrollIntoView({behavior: "smooth"});
312
314
  setRecordToEdit(item);
313
315
  setFormData(item);
314
316
  setDataEditorVisible(true);
317
+ setTimeout(() => {
318
+ dataEditorRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' });
319
+ }, 100);
315
320
  }}
316
321
  deleteApiCall={deleteApiCall}
317
322
  queryClient={queryClient}
@@ -586,13 +591,6 @@ function DataLayout({refreshUI}) {
586
591
  setImportModalVisible(false);
587
592
  }
588
593
 
589
- useEffect(() => {
590
- if( showDataEditor && mainPartRef.current ){
591
-
592
- }
593
- }, [showDataEditor,mainPartRef.current]);
594
-
595
-
596
594
  const [currentProfile, setCurrentProfile] = useLocalStorage('profile', null);
597
595
  const {isTourOpen, setIsTourOpen, currentTourSteps, allTourSteps, setTourStepIndex, setCurrentTourSteps, currentTour,setCurrentTour, addLaunchedTour, assistantConfig, setAssistantConfig} = useUI();
598
596
 
@@ -707,7 +705,7 @@ function DataLayout({refreshUI}) {
707
705
  onSelectWorkflow={(id) => {
708
706
  nav(`?edit-workflow=${id}`);
709
707
  setEditionMode(false)
710
- mainPartRef.current.scrollIntoView();
708
+ mainPartRef.current?.scrollIntoView();
711
709
  setCurrentView('workflow'); // Ajout pour forcer le changement de vue
712
710
  setWorkflowListModalOpen(false);
713
711
  }}
@@ -740,7 +738,7 @@ function DataLayout({refreshUI}) {
740
738
  setAPIInfoVisible(false);
741
739
  setDataEditorVisible(false);
742
740
  setEditionMode(true);
743
- mainPartRef.current.scrollIntoView({behavior: 'smooth'});
741
+ mainPartRef.current?.scrollIntoView({behavior: 'smooth'});
744
742
  gtag("event", "select_content", {
745
743
  content_type: "create_model",
746
744
  });
@@ -750,7 +748,7 @@ function DataLayout({refreshUI}) {
750
748
  setAPIInfoVisible(false);
751
749
  setEditionMode(true);
752
750
 
753
- mainPartRef.current.scrollIntoView({behavior: 'smooth'});
751
+ mainPartRef.current?.scrollIntoView({behavior: 'smooth'});
754
752
  gtag("event", "select_content", {
755
753
  content_type: "edit_model",
756
754
  content_id: model.name
@@ -760,7 +758,7 @@ function DataLayout({refreshUI}) {
760
758
  setDataEditorVisible(false);
761
759
  setEditionMode(false);
762
760
  setAPIInfoVisible(false);
763
- mainPartRef.current.scrollIntoView({behavior: 'smooth'});
761
+ //mainPartRef.current?.scrollIntoView({behavior: 'smooth'});
764
762
  gtag("event", "select_content", {
765
763
  content_type: "select_model",
766
764
  content_id: model.name
@@ -768,12 +766,12 @@ function DataLayout({refreshUI}) {
768
766
  }} onImportPack={() => {
769
767
  setShowPackGallery(true);
770
768
  }} onNewData={(model) => {
771
- mainPartRef.current.scrollIntoView({behavior: 'smooth'});
769
+ mainPartRef.current?.scrollIntoView({behavior: 'smooth'});
772
770
  handleAddData(model);
773
771
  }}/>
774
772
  {(editionMode) && (
775
773
  <ModelCreator ref={modelCreatorRef} onModelGenerated={() =>{
776
- modelCreatorRef.current.scrollIntoView({behavior: 'smooth'});
774
+ modelCreatorRef?.current?.scrollIntoView({behavior: 'smooth'});
777
775
  }} initialModel={selectedModel} onModelSaved={(model) => {
778
776
  setRefreshTime(new Date().getTime());
779
777
  handleModelSelect(model);
@@ -785,7 +783,8 @@ function DataLayout({refreshUI}) {
785
783
  )}
786
784
  <div className="hidden-anchor" ref={mainPartRef}></div>
787
785
 
788
- {showDataEditor && (<DataEditor ref={mainPartRef}
786
+ {showDataEditor && (<DataEditor
787
+ ref={dataEditorRef}
789
788
  key={selectedModel?.name}
790
789
  isLoading={isLoading}
791
790
  model={selectedModel}
@@ -738,10 +738,10 @@ export function DataTable({
738
738
  </button>)}
739
739
  {/* AJOUT : Bouton pour ouvrir l'éditeur de workflow */}
740
740
  {model.name === 'workflow' && (
741
- <NavLink to={`?edit-workflow=${item._id}`} className="datatable-action-btn workflow-edit"
741
+ <button onClick={() => nav(`?edit-workflow=${item._id}`)} className="btn-nav datatable-action-btn workflow-edit"
742
742
  data-tooltip-id="tooltipActions" data-tooltip-content={t('workflow.editor.title', "Éditeur de Workflow")}>
743
743
  <FaGear />
744
- </NavLink>
744
+ </button>
745
745
  )}
746
746
 
747
747
  <button data-tooltip-id="tooltipActions"
@@ -235,7 +235,7 @@
235
235
  }
236
236
  }
237
237
 
238
- @media only screen and (min-width: 1024Fpx){
238
+ @media only screen and (min-width: 1024px){
239
239
  .documentation-sidebar{
240
240
  max-width: 360px; // Largeur fixe pour le menu latéral
241
241
  }
@@ -17,7 +17,7 @@ const ViewSwitcher = ({ currentView, onViewChange, configuredViews, onConfigureV
17
17
  const { t } = useTranslation();
18
18
 
19
19
  return (
20
- <div className="view-switcher flex items-center gap-1 p-1 bg-gray-200 rounded-md">
20
+ <div className="view-switcher flex items-center gap-1 p-1 rounded-md">
21
21
  {viewOptions.map(view => {
22
22
  const isConfigured = view.id === 'table' || configuredViews[view.id];
23
23
  const isActive = currentView === view.id;
@@ -1,30 +1,31 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
-
4
- // https://vite.dev/config/
5
- export default defineConfig({
6
- plugins: [react()],
7
- base: "/",
8
- build: {
9
- outDir: "dist"
10
- },
11
- server : {
12
- proxy: {
13
- '/api': {
14
- target: 'http://localhost:7633',
15
- secure: false,
16
- changeOrigin: true,
17
- // Optionnel mais utile pour le débogage :
18
- // Affiche les requêtes proxy dans la console de Vite.
19
- configure: (proxy, options) => {
20
- proxy.on('proxyReq', (proxyReq, req, res) => {
21
- console.log(`[Vite Proxy] Forwarding request: ${req.method} ${req.url} -> ${options.target}${proxyReq.path}`);
22
- });
23
- }
24
- }
25
- }
26
- },
27
- fs: {
28
- allow: ["./src", "../src"]
29
- }
30
- })
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import { fileURLToPath, URL } from "node:url";
4
+
5
+ // https://vite.dev/config/
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ base: "/",
9
+ build: {
10
+ outDir: "dist"
11
+ },
12
+ server : {
13
+ proxy: {
14
+ '/api': {
15
+ target: 'http://localhost:7633',
16
+ secure: false,
17
+ changeOrigin: true,
18
+ // Optionnel mais utile pour le débogage :
19
+ // Affiche les requêtes proxy dans la console de Vite.
20
+ configure: (proxy, options) => {
21
+ proxy.on('proxyReq', (proxyReq, req, res) => {
22
+ console.log(`[Vite Proxy] Forwarding request: ${req.method} ${req.url} -> ${options.target}${proxyReq.path}`);
23
+ });
24
+ }
25
+ }
26
+ }
27
+ },
28
+ fs: {
29
+ allow: ["./src", "../src"]
30
+ }
31
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "data-primals-engine",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "data-primals-engine is a package responsible from handling large amount of data using MongoDB in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation. It also has integrated AI assistant.",
5
5
  "main": "src/engine.js",
6
6
  "type": "module",
@@ -28,10 +28,14 @@
28
28
  "@rollup/rollup-linux-x64-gnu": "4.6.1"
29
29
  },
30
30
  "resolutions": {
31
+ "shell-quote": ">=1.8.4",
32
+ "lodash": ">=4.18.0",
31
33
  "flatted": ">=3.4.2",
32
34
  "rollup": ">=4.59.0",
33
35
  "on-headers": ">=1.1.0",
34
- "brace-expansion": ">=2.0.2",
36
+ "brace-expansion": ">=2.0.3",
37
+ "minimatch": ">=3.1.4",
38
+ "path-to-regexp": ">=8.4.0",
35
39
  "prismjs": ">=1.30.0",
36
40
  "xml2js": ">=0.5.0",
37
41
  "tar-fs": ">=3.1.1",
@@ -39,13 +43,17 @@
39
43
  "node-forge": ">=1.4.0",
40
44
  "js-yaml": ">=4.1.1",
41
45
  "mdast-util-to-hast": ">=13.2.1",
46
+ "uuid": ">=11.1.1",
42
47
  "jws": ">=3.2.3",
43
- "undici": ">=6.23.0",
48
+ "undici": ">=7.28.0",
44
49
  "@xmldom/xmldom": ">=0.8.12",
45
50
  "picomatch": ">=4.0.4",
46
- "langsmith": ">=0.5.19",
51
+ "langsmith": ">=0.6.0",
47
52
  "qs": ">=6.14.2",
48
- "fast-xml-parser": ">=4.5.5"
53
+ "fast-xml-parser": ">=4.5.5",
54
+ "fast-xml-builder": ">=1.1.7",
55
+ "ip-address": ">=10.1.1",
56
+ "postcss": ">=8.5.10"
49
57
  },
50
58
  "overrides": {
51
59
  "react-syntax-highlighter": {
@@ -78,7 +86,7 @@
78
86
  },
79
87
  "dependencies": {
80
88
  "@langchain/core": "^0.3.80",
81
- "archiver": "^7.0.1",
89
+ "archiver": "^8.0.0",
82
90
  "aws-sdk": "^2.1692.0",
83
91
  "bcrypt": "^6.0.0",
84
92
  "body-parser": ">=2.2.1",
@@ -89,6 +97,7 @@
89
97
  "cronstrue": "^3.2.0",
90
98
  "csv-parse": "^6.1.0",
91
99
  "date-fns": "^4.1.0",
100
+ "dotenv": "^17.4.2",
92
101
  "express-csrf-double-submit-cookie": "^2.0.0",
93
102
  "express-formidable": "^1.2.0",
94
103
  "express-mongo-sanitize": "^2.2.0",
@@ -98,11 +107,12 @@
98
107
  "i18next-browser-languagedetector": "^8.2.0",
99
108
  "isolated-vm": "^5.0.4",
100
109
  "juice": "^11.0.3",
110
+ "leaflet": "^1.9.4",
101
111
  "mathjs": "15.2.0",
102
112
  "mongodb": "^6.18.0",
103
113
  "node-cache": "^5.1.2",
104
114
  "node-schedule": "^2.1.1",
105
- "nodemailer": "8.0.5",
115
+ "nodemailer": ">=9.0.1",
106
116
  "openai": "^6.9.1",
107
117
  "passport": "^0.7.0",
108
118
  "passport-saml-encrypted": "^0.1.13",
@@ -113,33 +123,33 @@
113
123
  "read-excel-file": "^6.0.1",
114
124
  "request-ip": "^3.3.0",
115
125
  "safe-regex": "^2.1.1",
116
- "sanitize-html": "^2.17.0",
126
+ "sanitize-html": "^2.17.4",
117
127
  "sirv": "^3.0.2",
118
128
  "stripe": "^20.0.0",
119
129
  "swagger-ui-express": "^5.0.1",
120
- "tar": "7.5.11",
130
+ "tar": ">=7.5.16",
121
131
  "tinycolor2": "^1.6.0",
122
132
  "uniqid": "^5.4.0",
123
- "vitest": "3.2.4",
133
+ "vitest": "3.2.6",
124
134
  "yaml": "2.8.3"
125
135
  },
126
136
  "peerDependencies": {
137
+ "@langchain/anthropic": "^0.3.33",
138
+ "@langchain/deepseek": "^0.1.0",
139
+ "@langchain/google-genai": "^0.2.18",
140
+ "@langchain/openai": "^0.6.16",
127
141
  "express": "^5.2.1",
128
142
  "passport-azure-ad": "^4.3.5",
129
143
  "passport-google-oauth20": "^2.0.0",
130
144
  "react": "18.3.1",
131
145
  "react-i18next": "^16.3.4",
132
- "react-query": "^3.39.3",
133
- "@langchain/anthropic": "^0.3.33",
134
- "@langchain/deepseek": "^0.1.0",
135
- "@langchain/google-genai": "^0.2.18",
136
- "@langchain/openai": "^0.6.16"
146
+ "react-query": "^3.39.3"
137
147
  },
138
148
  "devDependencies": {
139
- "@eslint/js": "^9.32.0",
149
+ "@eslint/js": "^9.39.4",
140
150
  "concurrently": "^9.2.0",
141
151
  "cross-env": "^10.0.0",
142
- "eslint": "^9.32.0",
152
+ "eslint": "^9.39.4",
143
153
  "globals": "^16.3.0"
144
154
  },
145
155
  "subPackages": [
package/src/ai.jobs.js ADDED
@@ -0,0 +1,135 @@
1
+ import {parseSafeJSON} from "./core.js";
2
+
3
+ export const generateModelViaAI = async (llm, lang, txt, history, existingModels = []) => {
4
+ const conditionBuilderExample = JSON.stringify({ "$and": [ { "fieldNameOfTypeRelation": { "$find": { "$eq": [ "$$this.code", "fr" ] } }}, {"$eq":["$fieldOtherTypes","valueToSearch"]}, { "$ne": { "$user": null } } ] }, null, 2);
5
+
6
+ try {
7
+ const messages = [
8
+ {
9
+ role: "system",
10
+ content:
11
+ `
12
+ Tu dois me retourner un objet JSON valide contenant les suggestions de modèles, basés sur la demande de l'utilisateur.
13
+ La spécification JSON d'UN modèle de données est la suivante :
14
+ Par exemple, pour "un modèle pour gérer une bibliothèque et ses livres", la réponse pourrait être:
15
+ {
16
+ "models": [{
17
+ "name": "book",
18
+ "icon": "FaBook",
19
+ "description": "*description détaillée en plusieurs phrases de l'utilité du modèle, et ses cas d'usage, ici : Modèle de référencement de livres multi-support. Peut être utilisé pour des bibliothèques personnelles, virtuelles ou municipales.*",
20
+ "fields": [
21
+ {"name": "title", "type": "string", "required": true, "asMain": true, "hint": "Titre du livre", color: '#FF89CC'},
22
+ {"name": "isbn", "type": "string", "unique": true, "hint": "Numéro ISBN"},
23
+ {"name": "author", "type": "relation", "relation": "author", "hint": "Auteur du livre", color: '#CCCCCC'},
24
+ {"name": "publicationDate", "type": "date", "default": "now", "hint": "Date de publication du livre"}
25
+ {"name": "bankCardNumber", "type": "text", "anonymized": true, "hint": "Numéro de carte bancaire anonymisé"}
26
+ ]
27
+ }
28
+ {
29
+ name: "library",
30
+ "description": "",
31
+ "icon": "FaBookOpenReader",
32
+ fields: [
33
+ {
34
+ "name": "name",
35
+ "type": "string",
36
+ asMain: true,
37
+ },
38
+ {
39
+ "name": "address",
40
+ "type": "string",
41
+ asMain: true,
42
+ },
43
+ {
44
+ "name": "city",
45
+ "type": "string",
46
+ asMain: true,
47
+ color: '#E63345'
48
+ },
49
+ {
50
+ "name": "postalCode",
51
+ "type": "string",
52
+ asMain: true,
53
+ },
54
+ {
55
+ "name": "country",
56
+ "type": "string",
57
+ color: "#4F80BF",
58
+ }
59
+ ]
60
+ }]
61
+ }
62
+
63
+ les types de champ autorisés sont :
64
+ string, string_t (translation key)
65
+ avec les propriétés { maxlength: number, multiline: boolean }
66
+
67
+ , password, url, phone, email,, richtext (html),
68
+ avec les propriétés { maxlength: number }
69
+ boolean
70
+ code
71
+ avec les propriétés { language: 'json', conditionBuilder: boolean }, avec conditionBuilder la prop pour construire des conditions sur les données
72
+ number
73
+ avec les propriétés { step: 0.1, unit: string, min: number, max: number }
74
+
75
+ date, datetime,
76
+ avec les propriétés { min: dateIso, max: dateIso }
77
+ enum,
78
+ avec les propriétés { items: ["label 1", "..."] }
79
+
80
+ array
81
+ avec les propriétés { itemsType: "string_t", ...et les autres propriétés du type itemsType }
82
+ relation
83
+ avec les propriétés { relationFilter: <condition>, multiple: boolean, relation: 'autreModeleNom'}, multiple permettant une relation 1-n pour chaque donnée du modèle.
84
+ Préfères l'utilisation du multiple et d'un modèle séparé plutôt qu'un type array avec itemsType=string.
85
+ Pour le type \`relation\`, utilise l'un des noms de modèles existants suivants : ${existingModels.join(', ')} ou le nom du modèle relationnel généré.\`
86
+ color
87
+ avec une valeur en héxadecimal, ex: '#FF0000'
88
+ file
89
+ pour gérer les documents
90
+ calculated
91
+ pour gérer les champs calculés (utile pour l'utilisateur)
92
+ model (nom du modele choisi),
93
+
94
+ cronSchedule
95
+ avec les propriétés { default: '* * * * * *', cronMask: [false, true, true, true, true, true])
96
+
97
+ <condition> ou la propriété 'condition' d'affichage sur les champs du modèle se définissent de cette manière :
98
+ ${conditionBuilderExample} avec les opérateurs mongodb $eq,$ne,$gt,$gte,$lt,$lte,$in,$nin,$dateAdd disponibles
99
+
100
+ Les modèles doivent être absolument nommés autrement que ceux déjà existants (donc pas ceux là : {${existingModels.join(', ')})
101
+ Si ils sont nommés pareil, renomme les tiens avec un suffixe '_2', ou '_3'...
102
+ Et assures-toi de générer les modèles que tu as introduits dans tes relations.
103
+
104
+ 'default' est la valeur par défaut que prendra le champ (string, number, boolean, or array of type supported)
105
+ Traduits le texte des hint et les données qui ne sont pas des variables ou des clés d'objet dans la langue iso2 ${lang}.
106
+
107
+ Mets obligatoirement des couleurs harmonieuses sur les champs asMain, ou ceux que tu considères comme importants, car ils doivent être mis en avant (selon le thème).
108
+
109
+ Ne renvoie bien QUE le tableau de modèles JSON, sans texte explicatif ni formatage markdown.`
110
+ },
111
+ {
112
+ role: "user",
113
+ content: typeof(txt) === 'string' ? txt.substring(0, 4096) : ''
114
+ },
115
+ ...history
116
+ ];
117
+
118
+ const completion = await llm.invoke(messages);
119
+
120
+ const aiResponse = completion.content;
121
+
122
+ try {
123
+ // parseSafeJSON fonctionnera que le contenu soit un objet ou un tableau
124
+ return parseSafeJSON(aiResponse); // On retourne directement le résultat parsé
125
+ } catch (e) {
126
+ console.error("Erreur de parsing du JSON de l'IA:", e);
127
+ throw new Error("Réponse invalide de l'IA.");
128
+ }
129
+
130
+ } catch (error) {
131
+ console.error("Error calling OpenAI or parsing response:", error);
132
+ // Renvoyer l'erreur pour que l'endpoint API puisse la gérer
133
+ throw error;
134
+ }
135
+ };