data-primals-engine 1.0.11 → 1.1.1

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 (167) hide show
  1. package/README.md +36 -0
  2. package/client/README.md +116 -0
  3. package/client/index.html +31 -0
  4. package/client/index.js +25 -0
  5. package/client/package-lock.json +11664 -0
  6. package/client/package.json +70 -0
  7. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  8. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  9. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
  10. package/client/public/doc/API.postman_collection.json +214 -0
  11. package/client/public/github.svg +1 -0
  12. package/client/public/profilCompany.jpg +0 -0
  13. package/client/public/profilDeveloper.jpg +0 -0
  14. package/client/public/profilEngineer.jpg +0 -0
  15. package/client/public/profilPersonal.jpg +0 -0
  16. package/client/public/profils .psd +0 -0
  17. package/client/public/react.svg +9 -0
  18. package/client/src/APIInfo.jsx +328 -0
  19. package/client/src/AddWidgetTypeModal.jsx +44 -0
  20. package/client/src/AddWidgetTypeModal.scss +87 -0
  21. package/client/src/App.css +42 -0
  22. package/client/src/App.jsx +588 -0
  23. package/client/src/App.scss +1466 -0
  24. package/client/src/AssistantChat.jsx +280 -0
  25. package/client/src/AssistantChat.scss +229 -0
  26. package/client/src/Button.jsx +12 -0
  27. package/client/src/Button.scss +197 -0
  28. package/client/src/CalculationBuilder.jsx +338 -0
  29. package/client/src/CalculationStepRow.jsx +199 -0
  30. package/client/src/CalendarConfigModal.jsx +49 -0
  31. package/client/src/ChartConfigModal.jsx +402 -0
  32. package/client/src/ConditionBuilder.jsx +622 -0
  33. package/client/src/ConditionBuilder.scss +672 -0
  34. package/client/src/ConditionBuilder2.jsx +757 -0
  35. package/client/src/ContentView.jsx +274 -0
  36. package/client/src/CronBuilder.jsx +126 -0
  37. package/client/src/CronBuilder.scss +128 -0
  38. package/client/src/CronPartBuilder.jsx +150 -0
  39. package/client/src/Dashboard.jsx +210 -0
  40. package/client/src/Dashboard.scss +445 -0
  41. package/client/src/DashboardChart.jsx +362 -0
  42. package/client/src/DashboardFlexViewItem.jsx +129 -0
  43. package/client/src/DashboardView.jsx +554 -0
  44. package/client/src/DataChart.jsx +349 -0
  45. package/client/src/DataEditor.jsx +556 -0
  46. package/client/src/DataLayout.jsx +709 -0
  47. package/client/src/DataTable.jsx +996 -0
  48. package/client/src/DataTable.scss +73 -0
  49. package/client/src/Dialog.jsx +90 -0
  50. package/client/src/Dialog.scss +117 -0
  51. package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
  52. package/client/src/DocumentationPageLayout.jsx +93 -0
  53. package/client/src/DocumentationPageLayout.scss +262 -0
  54. package/client/src/Draggable.jsx +222 -0
  55. package/client/src/Draggable.scss +49 -0
  56. package/client/src/ExportDialog.jsx +172 -0
  57. package/client/src/Field.jsx +1558 -0
  58. package/client/src/FlexBuilder.jsx +269 -0
  59. package/client/src/FlexBuilder.scss +226 -0
  60. package/client/src/FlexBuilderControls.jsx +170 -0
  61. package/client/src/FlexBuilderModal.jsx +75 -0
  62. package/client/src/FlexBuilderModal.scss +109 -0
  63. package/client/src/FlexBuilderPreview.jsx +48 -0
  64. package/client/src/FlexBuilderUtils.js +84 -0
  65. package/client/src/FlexDataRenderer.jsx +170 -0
  66. package/client/src/FlexDataRenderer.scss +80 -0
  67. package/client/src/FlexNode.jsx +290 -0
  68. package/client/src/FlexTreeUtils.js +129 -0
  69. package/client/src/HiddenableCell.jsx +46 -0
  70. package/client/src/HiddenableCell.scss +36 -0
  71. package/client/src/KPIDialog.jsx +39 -0
  72. package/client/src/KPIWidget.jsx +139 -0
  73. package/client/src/KanbanCard.jsx +92 -0
  74. package/client/src/KanbanColumn.jsx +29 -0
  75. package/client/src/KanbanConfigModal.jsx +115 -0
  76. package/client/src/KanbanView.jsx +113 -0
  77. package/client/src/KanbanView.scss +107 -0
  78. package/client/src/MessageRotator.jsx +77 -0
  79. package/client/src/MessageRotator.scss +14 -0
  80. package/client/src/ModelCreator.jsx +640 -0
  81. package/client/src/ModelCreator.scss +154 -0
  82. package/client/src/ModelCreatorField.jsx +810 -0
  83. package/client/src/ModelImporter.jsx +120 -0
  84. package/client/src/ModelList.jsx +205 -0
  85. package/client/src/Notification.jsx +137 -0
  86. package/client/src/Notification.scss +126 -0
  87. package/client/src/NotificationProvider.jsx +73 -0
  88. package/client/src/PackGallery.jsx +211 -0
  89. package/client/src/PackGallery.scss +156 -0
  90. package/client/src/Pagination.jsx +141 -0
  91. package/client/src/RTE.jsx +415 -0
  92. package/client/src/RTETrans.jsx +103 -0
  93. package/client/src/RelationField.jsx +247 -0
  94. package/client/src/RelationValue.jsx +230 -0
  95. package/client/src/RestoreConfirmationModal.jsx +183 -0
  96. package/client/src/RestoreDialog.jsx +134 -0
  97. package/client/src/RestoreDialog.scss +67 -0
  98. package/client/src/RichTextEditorModal.jsx +34 -0
  99. package/client/src/RichTextEditorModal.scss +54 -0
  100. package/client/src/TourSpotlight.jsx +234 -0
  101. package/client/src/TourSpotlight.scss +100 -0
  102. package/client/src/TutorialsMenu.jsx +83 -0
  103. package/client/src/TutorialsMenu.scss +217 -0
  104. package/client/src/TutorialsRewardModal.jsx +65 -0
  105. package/client/src/Utils.jsx +78 -0
  106. package/client/src/ViewSwitcher.jsx +54 -0
  107. package/client/src/ViewSwitcher.scss +74 -0
  108. package/client/src/Webpage.jsx +69 -0
  109. package/client/src/_variables.scss +21 -0
  110. package/client/src/assets/react.svg +1 -0
  111. package/client/src/constants.js +68 -0
  112. package/client/src/contexts/AuthContext.jsx +28 -0
  113. package/client/src/contexts/ModelContext.jsx +328 -0
  114. package/client/src/contexts/UIContext.jsx +52 -0
  115. package/client/src/core/data.js +35 -0
  116. package/client/src/entry-client.jsx +15 -0
  117. package/client/src/entry-server.jsx +13 -0
  118. package/client/src/filter.js +222 -0
  119. package/client/src/hooks/data.js +101 -0
  120. package/client/src/hooks/useDebounce.js +20 -0
  121. package/client/src/hooks/useDragAndDrop.js +59 -0
  122. package/client/src/hooks/useLocalStorage.js +100 -0
  123. package/client/src/hooks/useMount.js +42 -0
  124. package/client/src/hooks/useTutorials.jsx +227 -0
  125. package/client/src/hooks/useWindowSize.js +24 -0
  126. package/client/src/i18n.js +47 -0
  127. package/client/src/index.css +90 -0
  128. package/client/src/index.js +10 -0
  129. package/client/src/main.jsx +11 -0
  130. package/client/src/translations.js +15940 -0
  131. package/client/src/tutorials.js +112 -0
  132. package/client/vite.config.js +30 -0
  133. package/eslint.config.js +50 -0
  134. package/package.json +51 -27
  135. package/server.js +9 -0
  136. package/src/constants.js +12 -23
  137. package/src/core.js +9 -8
  138. package/src/data.js +81 -80
  139. package/src/defaultModels.js +114 -115
  140. package/src/email.js +8 -9
  141. package/src/engine.js +5 -5
  142. package/src/events.js +1 -2
  143. package/src/i18n.js +212 -211
  144. package/src/middlewares/middleware-mongodb.js +94 -93
  145. package/src/migrate.js +24 -24
  146. package/src/modules/assistant.js +55 -55
  147. package/src/modules/bucket.js +10 -9
  148. package/src/modules/data.js +387 -357
  149. package/src/modules/file.js +3 -6
  150. package/src/modules/mongodb.js +6 -7
  151. package/src/modules/user.js +24 -47
  152. package/src/modules/workflow.js +139 -73
  153. package/src/openai.jobs.js +2 -2
  154. package/src/packs.js +30 -31
  155. package/src/providers.js +101 -3
  156. package/src/setenv.js +1 -1
  157. package/src/user.js +0 -17
  158. package/src/workers/crypto-worker.js +21 -20
  159. package/src/workers/import-export-worker.js +18 -18
  160. package/test/data.backup.integration.test.js +7 -12
  161. package/test/data.integration.test.js +27 -25
  162. package/test/globalSetup.js +2 -2
  163. package/test/import_export.integration.test.js +6 -6
  164. package/test/model.integration.test.js +21 -22
  165. package/test/workflow.integration.test.js +2 -5
  166. package/test/workflow.robustness.test.js +6 -13
  167. package/vitest.config.js +2 -2
@@ -0,0 +1,588 @@
1
+ import React, {useState, useEffect, useMemo, Suspense, lazy, useReducer, useRef} from 'react';
2
+
3
+ import "yet-another-react-lightbox/plugins/captions.css";
4
+ import "yet-another-react-lightbox/styles.css";
5
+ import "./App.scss";
6
+ import {QueryClient, QueryClientProvider, useMutation, useQuery} from "react-query";
7
+
8
+ import {
9
+ BrowserRouter,
10
+ Outlet,
11
+ Route,
12
+ Routes,
13
+ useLocation,
14
+ useNavigate,
15
+ useParams,
16
+ useSearchParams
17
+ } from "react-router-dom";
18
+ import {Trans, useTranslation} from "react-i18next";
19
+ import "../../src/i18n.js"
20
+ import "../src/i18n.js"
21
+
22
+ import {
23
+ ModelProvider, useModelContext,
24
+ AuthProvider, useAuthContext,
25
+ UIProvider, useUI,
26
+ NotificationProvider, useNotificationContext,
27
+ useLocalStorage,
28
+ DataLayout,
29
+ Webpage,
30
+ SelectField, TextField,
31
+ Button,
32
+ APIInfo,
33
+ NotificationList,
34
+ DashboardsPage,
35
+ RestoreDialog,
36
+ MessageRotator,
37
+ DocumentationPageLayout,
38
+ ContentView,
39
+ AssistantChat
40
+ } from '../index.js';
41
+
42
+ import {
43
+ FaBell,
44
+ FaDatabase, FaDiscord, FaFacebook, FaGithub,
45
+ FaHome,
46
+ FaInfo,
47
+ FaKey,
48
+ FaLanguage,
49
+ FaMobile,
50
+ FaStar
51
+ } from "react-icons/fa";
52
+ import {Helmet} from "react-helmet";
53
+ import {useCookies, CookiesProvider} from "react-cookie";
54
+
55
+ import { translations as allTranslations} from "data-primals-engine/i18n";
56
+ import {getRandom} from "data-primals-engine/core";
57
+ import {getUserHash} from "data-primals-engine/data";
58
+ import {promotionalMessages, seoTitle} from "./constants.js";
59
+ import i18next from "i18next";
60
+ import {websiteTranslations} from "./translations.js";
61
+
62
+ let queryClient = new QueryClient();
63
+
64
+ export const setQueryClient = (q) => {
65
+ queryClient = q;
66
+ };
67
+
68
+ function TopBar({header}) {
69
+
70
+ const location = useLocation();
71
+ const { i18n, t } = useTranslation();
72
+ const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
73
+
74
+
75
+ useEffect(() => {
76
+ if( location.pathname ){
77
+ gtag('config', 'G-NDHNSVB4YB', { page_path: location.pathname });
78
+ }
79
+ }, [location]);
80
+ const searchParams = useSearchParams();
81
+ return <>{header}</>;
82
+ }
83
+
84
+ function Layout ({header, translationMutation, routes, body, footer}) {
85
+ const [cookies, setCookie, removeCookie] = useCookies(['username']);
86
+ const { i18n, t } = useTranslation();
87
+ const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
88
+ const {models= [], setGeneratedModels } = useModelContext();
89
+ const { me, setMe } = useAuthContext();
90
+ // 2. NOUVEAU : État pour stocker la configuration de l'assistant
91
+ const [assistantConfig, setAssistantConfig] = useState(null);
92
+
93
+ useQuery(
94
+ 'assistantConfig', // La clé de la requête reste la même
95
+ () => {
96
+ // On appelle l'endpoint de recherche générique
97
+ return fetch('/api/data/search', {
98
+ method: 'POST',
99
+ headers: {
100
+ 'Content-Type': 'application/json',
101
+ // L'authentification sera gérée par le contexte ou un intercepteur
102
+ },
103
+ body: JSON.stringify({
104
+ model: 'env', // On cible le modèle 'env'
105
+ filter: {
106
+ // On cherche les enregistrements dont le nom est SOIT l'un, SOIT l'autre
107
+ "$or": [
108
+ { "name": "OPENAI_API_KEY" },
109
+ { "name": "GOOGLE_API_KEY" }
110
+ ]
111
+ },
112
+ limit: 2 // Pas besoin de plus de 2 résultats
113
+ })
114
+ }).then(res => res.json());
115
+ },
116
+ {
117
+ enabled: !!me && models.find(f => f.name === 'env' && me?.username === f.username), // Toujours activé uniquement si l'utilisateur est connecté
118
+ staleTime: 60000, // On peut réduire le staleTime, car l'utilisateur peut ajouter/supprimer une clé à tout moment
119
+ onSuccess: (response) => {
120
+
121
+ // La réponse de l'API de recherche est un objet { success: true, data: [...] }
122
+ if (!response.data) {
123
+ setAssistantConfig(null);
124
+ return;
125
+ }
126
+
127
+ const availableKeys = response.data;
128
+
129
+ const newConfig = {
130
+ openai: availableKeys.find(key => key.name === 'OPENAI_API_KEY')?.value,
131
+ google: availableKeys.find(key => key.name === 'GOOGLE_API_KEY')?.value
132
+ };
133
+
134
+
135
+ console.log(newConfig)
136
+ // On met à jour l'état si au moins une clé est disponible
137
+ if (newConfig.openai || newConfig.google) {
138
+ setAssistantConfig(newConfig);
139
+ } else {
140
+ // Important : on s'assure de remettre à null si aucune clé n'est trouvée
141
+ setAssistantConfig(null);
142
+ }
143
+ },
144
+ retry: false,
145
+ }
146
+ );
147
+
148
+
149
+ const [lastLang, setLastLang] = useState(null);
150
+
151
+ // AJOUT : Liste des suggestions de prompts
152
+ const suggestedPrompts = [
153
+ {
154
+ title: t('suggested_prompt.movies.title', 'Collection de films'),
155
+ prompt: t('suggested_prompt.movies.prompt', 'Une liste de mes films préférés avec leur réalisateur, année de sortie et une note sur 5.')
156
+ },
157
+ {
158
+ title: t('suggested_prompt.inventory.title', 'Inventaire de boutique'),
159
+ prompt: t('suggested_prompt.inventory.prompt', "Un inventaire simple pour ma petite boutique en ligne, avec le nom du produit, le prix et la quantité en stock.")
160
+ },
161
+ {
162
+ title: t('suggested_prompt.expenses.title', 'Suivi de dépenses'),
163
+ prompt: t('suggested_prompt.expenses.prompt', "Un suivi de mes dépenses mensuelles, avec une description, un montant et une catégorie (nourriture, transport, loisirs...).")
164
+ },
165
+ {
166
+ title: t('suggested_prompt.recipes.title', 'Recettes de cuisine'),
167
+ prompt: t('suggested_prompt.recipes.prompt', "Une collection de recettes de cuisine, avec les ingrédients, les instructions et le temps de préparation.")
168
+ }
169
+ ];
170
+
171
+ const promotionalMessages = [
172
+ { text: t('promo.rotation.6') },
173
+ { text: t('promo.rotation.1') },
174
+ { text: t('promo.rotation.2') },
175
+ { text: t('promo.rotation.1') },
176
+ { text: t('promo.rotation.4') },
177
+ { text: t('promo.rotation.5') }
178
+ ];
179
+ const { setCurrentTourSteps, setAllTourSteps, currentTour, setCurrentTour } = useUI();
180
+ const [translations, setTranslations] = useState([]);
181
+
182
+
183
+ const isProd = import.meta.env.MODE === 'production';
184
+ const loc = useLocation();
185
+
186
+ const changeLanguage = (newLang) => {
187
+ if (typeof(newLang) === 'string' && newLang) {
188
+
189
+ i18n.changeLanguage(newLang, (err)=>{
190
+
191
+ i18next.removeResourceBundle(lang, "translation");
192
+ i18next.addResourceBundle(newLang, 'translation', {...websiteTranslations[newLang]['translation'], ...translations[newLang]?.['translation']});
193
+
194
+ gtag("event", "change_language "+newLang);
195
+ });
196
+ }
197
+ };
198
+
199
+ useEffect(() => {
200
+ if( me ){
201
+ translationMutation?.mutateAsync(lang).then(d => {
202
+ setTranslations(d);
203
+ setLastLang(lang);
204
+ });
205
+ }
206
+ }, [me, lang])
207
+
208
+ useEffect(() => {
209
+ changeLanguage(lang);
210
+ }, [lang]);
211
+
212
+ const [currentProfile, setCurrentProfile] = useLocalStorage('profile', null);
213
+
214
+ const showDatas = () => {
215
+ if( me )
216
+ nav('/user/' + getUserHash(me) + '/');
217
+ }
218
+
219
+ const showWebsite = () => {
220
+ if( me ) {
221
+ nav('/user/' + getUserHash(me) + '/cms/');
222
+ }
223
+ }
224
+
225
+ useEffect(() => {
226
+ if (Array.isArray(translations)) {
227
+ var trs= {};
228
+ translations.forEach(tr =>{
229
+ trs[tr.key] = tr.value;
230
+ });
231
+ trs= { ...allTranslations[lang]['translation'], ...trs};
232
+
233
+ if( lastLang && lang !== lastLang) {
234
+ i18n.removeResourceBundle(lastLang, 'translation');
235
+ }
236
+ i18n.addResourceBundle(lang, 'translation', trs);
237
+ }
238
+ }, [translations]);
239
+
240
+ useEffect(() => {
241
+ changeLanguage(lang);
242
+ }, []);
243
+
244
+ const [refreshReducer, refreshUI]= useReducer((n) => n+1, 0,() => 0);
245
+
246
+ const nav = useNavigate();
247
+
248
+ const [red, triggerSignin] = useReducer((d) => d+1, 0, () => 0);
249
+
250
+ const [ activeTuto ] = useLocalStorage('activeTuto', null);
251
+ const menu=<>{(!me) && !loc.pathname.startsWith("/user/demo") && (<Button className={"btn btn-nav btn-ellipsis btn-primary btn-big"} onClick={() => {
252
+ gtag("event", "nav to demo");
253
+ setCurrentProfile(null);
254
+ const username = 'demo'+getRandom(1, 99);
255
+ setMe({username});
256
+ setCookie('username', username, { path : "/", domain: isProd ? 'primals.net' : 'localhost'});
257
+ nav('/user/'+username, { state: { startTour: true } });
258
+ }}><Trans i18nKey={"links.demo"}>Demo</Trans></Button>)}</>
259
+
260
+ const onAuthenticated = (me, signedIn) => {
261
+ if(/^demo[0-9]{1,2}$/.test(me?.username)) {
262
+ setMe({...me, activeTutorial : activeTuto});
263
+ gtag("event", "signed_in (demo)");
264
+ }
265
+ else if(me){
266
+ setMe(me);
267
+ gtag("event", "signed_in");
268
+ }
269
+
270
+ if (signedIn && me && !loc.pathname.startsWith("/user/"+getUserHash(me)))
271
+ nav("/user/" + getUserHash(me) + "/");
272
+ };
273
+
274
+ useEffect(() => {
275
+ if( !cookies.username)
276
+ onAuthenticated({username: 'demo'+getRandom(1, 99)}, true);
277
+ }, [cookies.username]);
278
+
279
+
280
+ const dashboardModelFound = models.find(f => f.name === 'dashboard' && f._user === me?.username);
281
+ const outlet = useMemo(() => <>
282
+ <TopBar header={header} />
283
+ <div id={"content"}>
284
+ <Outlet />
285
+ </div>
286
+ </>, [red, me]);
287
+
288
+ const [lightboxOpened, setLightboxOpened] = React.useState(false);
289
+ const [lightboxIndex, setLightboxIndex] = React.useState(0);
290
+
291
+ const slides = [{src: "/prez1.jpg", title: t('prez1')},{src: "/prez6.jpg", title: i18n.t('prez3')},{src: "/prez2.jpg", title: t('prez2')},{src: "/prez4.jpg", title: i18n.t('prez4')},{src: "/prez5.jpg", title: i18n.t('prez5')}];
292
+
293
+ const { addNotification } = useNotificationContext();
294
+
295
+ // Ce hook est maintenant correct car `addNotification` vient du bon contexte.
296
+ useEffect(() => {
297
+ if (!me || !me.username) {
298
+ return;
299
+ }
300
+
301
+ let eventSource;
302
+
303
+ const connect = () => {
304
+ const url = `/api/alerts/subscribe?_user=${encodeURIComponent(me.username)}`;
305
+ eventSource = new EventSource(url);
306
+
307
+ eventSource.onopen = () => {
308
+ //console.log('[SSE] Connexion au serveur lie pour les alertes.');
309
+ };
310
+
311
+ eventSource.onmessage = (event) => {
312
+ try {
313
+ const data = JSON.parse(event.data);
314
+ //console.log('[SSE] Données reçues:', data);
315
+
316
+ if (data.type === 'cron_alert') {
317
+ // Cet appel va maintenant fonctionner et afficher la notification
318
+ addNotification({
319
+ title: data.message,
320
+ description: data.message,
321
+ status: 'info',
322
+ icon: <FaBell />,
323
+ timeout: 60000
324
+ });
325
+ } else if (data.type === 'connection_established') {
326
+ //console.log(`[SSE] ${data.message}`);
327
+ }
328
+
329
+ } catch (error) {
330
+ //console.error('[SSE] Erreur lors du parsing du message reçu:', error);
331
+ }
332
+ };
333
+
334
+ eventSource.onerror = (error) => {
335
+ //console.error('[SSE] Erreur EventSource:', error);
336
+ if (eventSource.readyState === EventSource.CLOSED) {
337
+ //console.log('[SSE] La connexion a été fermée.');
338
+ }
339
+ eventSource.close();
340
+ };
341
+ }
342
+
343
+ connect();
344
+
345
+ return () => {
346
+ if (eventSource) {
347
+ //console.log('[SSE] Fermeture de la connexion au serveur.');
348
+ eventSource.close();
349
+ }
350
+ };
351
+ }, [me, addNotification, t]);
352
+ const [prompt, setPrompt] = useLocalStorage("ai_model_prompt", null);
353
+ const [promptResult, setPromptResult] = useLocalStorage("ai_model_prompt_result", null);
354
+ const handleGenerateClick = (p) => {
355
+ gtag('event', 'homepage model generation');
356
+ setCurrentTour(null);
357
+ const username = 'demo'+getRandom(0, 99);
358
+ setMe({username});
359
+
360
+ setPromptResult(null);
361
+ setCookie('username', username, { path : "/", domain: isProd ? 'primals.net' : 'localhost'});
362
+ nav('/user/'+username, { state: { startTour: false } });
363
+ };
364
+
365
+ const myInputRef = useRef(null);
366
+
367
+ return <div className={['ar', 'fa'].includes(lang)? 'rtl' : 'ltr'}>
368
+ <NotificationList />
369
+ <><Helmet><title>
370
+ {t('seo.title', seoTitle)}</title></Helmet></>
371
+ <Routes>
372
+ <Route path={"/backup/restore"} element={<>
373
+ <RestoreDialog />
374
+ </>} />
375
+ <Route path={"/"} element={outlet}>
376
+ <Route path={"/documentation/*"} element={<DocumentationPageLayout topBarMenu={menu} />}>
377
+ <Route index element={<APIInfo />} />
378
+ {/*
379
+ <Route path="api/reference/:modelName" element={<APIInfo />} />
380
+ <Route path="advanced/features" element={<AdvancedFeaturesPage />} />*/}
381
+ </Route>
382
+ <Route path={""} element={<>
383
+
384
+ <div className="flex flex-row flex-no-wrap">
385
+ <div className="flex flex-1 flex-no-gap home-header">
386
+ <a href={"https://github.com/anonympins/data-primals-engine"} target={"_blank"} className="link-top"><img src={"/github.svg"} alt={"Github"} /></a>
387
+ <div className="flex prior">
388
+ <img
389
+ src="https://web.primals.net/PRIOR.png"
390
+ alt={"Prior, the Primals.net mascotte !"}
391
+ width={250}
392
+ />
393
+ <div className="bubble flex-1">
394
+ <h1><Trans i18nKey={"prez.title"}>{seoTitle}</Trans></h1>
395
+ <div className="inner"><MessageRotator messages={promotionalMessages} fadeDuration={200} defaultDelay={7500}/></div>
396
+ </div>
397
+ </div>
398
+ <div className="flex flex-row">
399
+ {menu}
400
+ </div>
401
+ <div className="ai-home-prompt-section">
402
+ <h2>{t('home.ai_prompt.title')}</h2>
403
+ <TextField
404
+ ref={myInputRef}
405
+ multiline
406
+ value={prompt}
407
+ onChange={(e) => setPrompt(e.target.value)}
408
+ placeholder={t('home.ai_prompt.placeholder', "Décrivez quel genre de données vous voulez stocker (produits, clients, pages web...), ou envoyez simplement votre idée à l'IA !")}
409
+ />
410
+ <Button onClick={() => handleGenerateClick(prompt)}>
411
+ {t('home.ai_prompt.button', "Générer mon modèle")}
412
+ </Button>
413
+ <div className="suggested-prompts">
414
+ <p>{t('home.ai_prompt.suggestions_title', 'Ou essayez une de nos suggestions :')}</p>
415
+ <ul>
416
+ {suggestedPrompts.map((p, index) => (
417
+ <li key={index} onClick={() => {
418
+ setPrompt(p.prompt);
419
+ setTimeout(() => handleGenerateClick(), 200);
420
+ // myInputRef.current.scrollIntoView({ behavior: 'smooth'});
421
+ }}>
422
+ <strong>{p.title}</strong>
423
+ <span>{p.prompt}</span>
424
+ </li>
425
+ ))}
426
+ </ul>
427
+ </div>
428
+ </div>
429
+
430
+ </div>
431
+ {body}
432
+ </div>
433
+ <div className="actions flex">{menu}</div>
434
+ </>}/>
435
+
436
+ <Route path={"/user/:user/cms/*"} element={<>
437
+ {me && (<Webpage/>)}
438
+ {!me && (<></>)}
439
+ </>}/>
440
+
441
+ <Route path="/user/:user/dashboards/:hash?" element={<>
442
+ {menu}
443
+ {me && <DashboardsPage />}
444
+ </>} />
445
+
446
+ <Route path={"/user/:user/*"} element={<>
447
+ {menu}
448
+ <UserPage notifs={[]} onAuthenticated={onAuthenticated} triggerSignin={triggerSignin}/>
449
+ {!me && (<></>)}
450
+ </>}/>
451
+ <Route path={"/:lang/*"} element={<>
452
+ {menu}
453
+ <ContentView/>
454
+ </>}/>
455
+
456
+ {routes}
457
+ </Route>
458
+ </Routes>
459
+ <footer className="flex flex-centered">
460
+ {footer}
461
+ </footer>
462
+ {me && <AssistantChat config={assistantConfig} />}
463
+ </div>;
464
+ }
465
+
466
+ function UserPage({notifs,triggerSignin, onAuthenticated}) {
467
+ const {me} = useAuthContext()
468
+ const [cookies, setCookie, removeCookie] = useCookies(['username']);
469
+ const params = useParams();
470
+ const [started, setStarted] = useState(false);
471
+ const {t } = useTranslation()
472
+
473
+ const [prompt, setPrompt] = useLocalStorage("ai_model_prompt", null);
474
+ const allTourSteps = {
475
+ demo: [
476
+ {
477
+ selector: '.tourStep-create-model', // Sélecteur CSS de l'élément
478
+ content: t('tourpoint.createModel'),
479
+ position: 'top', // Position de l'info-bulle par rapport à l'élément
480
+ },
481
+ {
482
+ selector: '.tourStep-import-model',
483
+ content: t('tourpoint.importModel'),
484
+ position: 'top',
485
+ },
486
+ {
487
+ selector: '.tourStep-profile',
488
+ content: t('tourpoint.profile'),
489
+ position: 'bottom',
490
+ },
491
+ ],
492
+ datapacks: [
493
+ {
494
+ selector: '.tourStep-import-datapack',
495
+ content: t('tourpoint.importDatapack'),
496
+ position: 'bottom'
497
+ }
498
+ ],
499
+ personal: [
500
+ {
501
+ selector: '.model-list [data-testid=model_budget]', // Example target, adjust as needed
502
+ content: t('tourpoint.model-list-budget', 'Gérez votre budget et votre portefeuille avec précision !'),
503
+ placement: 'bottom',
504
+ },
505
+ {
506
+ selector: '.model-list [data-testid=model_contact]', // Example target, adjust as needed
507
+ content: t('tourpoint.model-list-contact', 'Ne perdez plus les coordonnées de vos contacts !'),
508
+ placement: 'bottom',
509
+ },
510
+ {
511
+ selector: '.model-list [data-testid=model_imageGallery]', // Example target, adjust as needed
512
+ content: t('tourpoint.model-list-imageGallery', 'Classez vos images, photos, illustrations...'),
513
+ placement: 'bottom',
514
+ },
515
+ ],
516
+ developer: [
517
+ {
518
+ selector: '.btn[data-testid=btn-documentation]',
519
+ content: t('tourpoint.doc', 'Prenez connaissance de la documentation, pour vous aider à utiliser l\'API ou le mode bac à sable.'),
520
+ placement: 'bottom',
521
+ },
522
+ ],
523
+ company: [
524
+ {
525
+ selector: '.btn[data-testid=btn-dashboards]',
526
+ content: t('tourpoint.dashboards', 'Vos dashboards vous permettent de construire et suivre votre activité en temps réel.'),
527
+ placement: 'bottom',
528
+ },
529
+ ],
530
+ engineer: [
531
+ // ... steps specific to the engineer profile, focusing on workflow ...
532
+ {
533
+ selector: '.model-list-search-bar-container', // Example target, adjust as needed
534
+ content: t('tourpoint.workflows', 'Tapez "workflow" dans la barre de recherche pour obtenir les modèles d\'automatisation, ou tout autre mot-clé.'),
535
+ placement: 'bottom',
536
+ },
537
+ // ... more steps about workflow configuration ...
538
+ ],
539
+ };
540
+ const id = getUserHash(me)+'';
541
+
542
+ // NOUVEAU : On récupère l'état de la navigation
543
+ const location = useLocation();
544
+ const shouldStartTour = location.state?.startTour;
545
+ const isDemo = /^demo[0-9]{1,2}$/.test(me?.username);
546
+ const { setCurrentTourSteps, setAllTourSteps, launchedTours } = useUI();
547
+
548
+ useEffect(() => {
549
+ if( cookies.username){
550
+ onAuthenticated({username: cookies.username}, true);
551
+ }
552
+ }, [cookies.username]);
553
+ useEffect(() => {
554
+ if( isDemo && shouldStartTour){
555
+ setCurrentTourSteps(allTourSteps.demo);
556
+ }
557
+ setAllTourSteps(allTourSteps);
558
+ }, [isDemo, shouldStartTour]);
559
+
560
+ return <>{/^demo[0-9]{1,2}$/.test(me?.username) && notifs}
561
+ {params.user === id && <DataLayout/>}
562
+ {params.user !== id &&
563
+ <p>Veuillez vous authentifier avec cet utilisateur &quot;{params.user}&quot; pour accéder à vos
564
+ données</p>}</>;
565
+ }
566
+
567
+ function App() {
568
+
569
+ return (
570
+ <QueryClientProvider client={queryClient}>
571
+ <AuthProvider>
572
+ <CookiesProvider>
573
+ <ModelProvider>
574
+ <BrowserRouter>
575
+ <UIProvider>
576
+ <NotificationProvider>
577
+ <Layout />
578
+ </NotificationProvider>
579
+ </UIProvider>
580
+ </BrowserRouter>
581
+ </ModelProvider>
582
+ </CookiesProvider>
583
+ </AuthProvider>
584
+ </QueryClientProvider>
585
+ );
586
+ }
587
+
588
+ export default App;