data-primals-engine 1.0.10 → 1.1.0

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