data-primals-engine 1.0.11 → 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.
- package/client/README.md +8 -0
- package/client/index.html +31 -0
- package/client/index.js +25 -0
- package/client/package-lock.json +11664 -0
- package/client/package.json +70 -0
- package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
- package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
- package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
- package/client/public/doc/API.postman_collection.json +214 -0
- package/client/public/github.svg +1 -0
- package/client/public/profilCompany.jpg +0 -0
- package/client/public/profilDeveloper.jpg +0 -0
- package/client/public/profilEngineer.jpg +0 -0
- package/client/public/profilPersonal.jpg +0 -0
- package/client/public/profils .psd +0 -0
- package/client/public/react.svg +9 -0
- package/client/src/APIInfo.jsx +328 -0
- package/client/src/AddWidgetTypeModal.jsx +44 -0
- package/client/src/AddWidgetTypeModal.scss +87 -0
- package/client/src/App.css +42 -0
- package/client/src/App.jsx +586 -0
- package/client/src/App.scss +1466 -0
- package/client/src/AssistantChat.jsx +280 -0
- package/client/src/AssistantChat.scss +229 -0
- package/client/src/Button.jsx +12 -0
- package/client/src/Button.scss +197 -0
- package/client/src/CalculationBuilder.jsx +338 -0
- package/client/src/CalculationStepRow.jsx +199 -0
- package/client/src/CalendarConfigModal.jsx +49 -0
- package/client/src/ChartConfigModal.jsx +402 -0
- package/client/src/ConditionBuilder.jsx +622 -0
- package/client/src/ConditionBuilder.scss +672 -0
- package/client/src/ConditionBuilder2.jsx +757 -0
- package/client/src/ContentView.jsx +274 -0
- package/client/src/CronBuilder.jsx +126 -0
- package/client/src/CronBuilder.scss +128 -0
- package/client/src/CronPartBuilder.jsx +150 -0
- package/client/src/Dashboard.jsx +210 -0
- package/client/src/Dashboard.scss +445 -0
- package/client/src/DashboardChart.jsx +362 -0
- package/client/src/DashboardFlexViewItem.jsx +129 -0
- package/client/src/DashboardView.jsx +554 -0
- package/client/src/DataChart.jsx +349 -0
- package/client/src/DataEditor.jsx +556 -0
- package/client/src/DataLayout.jsx +709 -0
- package/client/src/DataTable.jsx +996 -0
- package/client/src/DataTable.scss +73 -0
- package/client/src/Dialog.jsx +90 -0
- package/client/src/Dialog.scss +117 -0
- package/client/src/DisplayFlexNodeRenderer.jsx +115 -0
- package/client/src/DocumentationPageLayout.jsx +93 -0
- package/client/src/DocumentationPageLayout.scss +262 -0
- package/client/src/Draggable.jsx +222 -0
- package/client/src/Draggable.scss +49 -0
- package/client/src/ExportDialog.jsx +172 -0
- package/client/src/Field.jsx +1558 -0
- package/client/src/FlexBuilder.jsx +269 -0
- package/client/src/FlexBuilder.scss +226 -0
- package/client/src/FlexBuilderControls.jsx +170 -0
- package/client/src/FlexBuilderModal.jsx +75 -0
- package/client/src/FlexBuilderModal.scss +109 -0
- package/client/src/FlexBuilderPreview.jsx +48 -0
- package/client/src/FlexBuilderUtils.js +84 -0
- package/client/src/FlexDataRenderer.jsx +170 -0
- package/client/src/FlexDataRenderer.scss +80 -0
- package/client/src/FlexNode.jsx +290 -0
- package/client/src/FlexTreeUtils.js +129 -0
- package/client/src/HiddenableCell.jsx +46 -0
- package/client/src/HiddenableCell.scss +36 -0
- package/client/src/KPIDialog.jsx +39 -0
- package/client/src/KPIWidget.jsx +139 -0
- package/client/src/KanbanCard.jsx +92 -0
- package/client/src/KanbanColumn.jsx +29 -0
- package/client/src/KanbanConfigModal.jsx +115 -0
- package/client/src/KanbanView.jsx +113 -0
- package/client/src/KanbanView.scss +107 -0
- package/client/src/MessageRotator.jsx +77 -0
- package/client/src/MessageRotator.scss +14 -0
- package/client/src/ModelCreator.jsx +640 -0
- package/client/src/ModelCreator.scss +154 -0
- package/client/src/ModelCreatorField.jsx +810 -0
- package/client/src/ModelImporter.jsx +120 -0
- package/client/src/ModelList.jsx +205 -0
- package/client/src/Notification.jsx +137 -0
- package/client/src/Notification.scss +126 -0
- package/client/src/NotificationProvider.jsx +73 -0
- package/client/src/PackGallery.jsx +211 -0
- package/client/src/PackGallery.scss +156 -0
- package/client/src/Pagination.jsx +141 -0
- package/client/src/RTE.jsx +415 -0
- package/client/src/RTETrans.jsx +103 -0
- package/client/src/RelationField.jsx +247 -0
- package/client/src/RelationValue.jsx +230 -0
- package/client/src/RestoreConfirmationModal.jsx +183 -0
- package/client/src/RestoreDialog.jsx +134 -0
- package/client/src/RestoreDialog.scss +67 -0
- package/client/src/RichTextEditorModal.jsx +34 -0
- package/client/src/RichTextEditorModal.scss +54 -0
- package/client/src/TourSpotlight.jsx +234 -0
- package/client/src/TourSpotlight.scss +100 -0
- package/client/src/TutorialsMenu.jsx +83 -0
- package/client/src/TutorialsMenu.scss +217 -0
- package/client/src/TutorialsRewardModal.jsx +65 -0
- package/client/src/Utils.jsx +78 -0
- package/client/src/ViewSwitcher.jsx +54 -0
- package/client/src/ViewSwitcher.scss +74 -0
- package/client/src/Webpage.jsx +69 -0
- package/client/src/_variables.scss +21 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/constants.js +68 -0
- package/client/src/contexts/AuthContext.jsx +28 -0
- package/client/src/contexts/ModelContext.jsx +328 -0
- package/client/src/contexts/UIContext.jsx +52 -0
- package/client/src/core/data.js +35 -0
- package/client/src/entry-client.jsx +15 -0
- package/client/src/entry-server.jsx +13 -0
- package/client/src/filter.js +222 -0
- package/client/src/hooks/data.js +101 -0
- package/client/src/hooks/useDebounce.js +20 -0
- package/client/src/hooks/useDragAndDrop.js +59 -0
- package/client/src/hooks/useLocalStorage.js +100 -0
- package/client/src/hooks/useMount.js +42 -0
- package/client/src/hooks/useTutorials.jsx +227 -0
- package/client/src/hooks/useWindowSize.js +24 -0
- package/client/src/i18n.js +47 -0
- package/client/src/index.css +90 -0
- package/client/src/index.js +10 -0
- package/client/src/main.jsx +11 -0
- package/client/src/translations.js +15940 -0
- package/client/src/tutorials.js +112 -0
- package/client/vite.config.js +30 -0
- package/eslint.config.js +50 -0
- package/package.json +48 -27
- package/server.js +9 -0
- package/src/constants.js +12 -23
- package/src/core.js +9 -8
- package/src/data.js +81 -80
- package/src/defaultModels.js +114 -115
- package/src/email.js +8 -9
- package/src/engine.js +5 -5
- package/src/events.js +1 -2
- package/src/i18n.js +212 -211
- package/src/middlewares/middleware-mongodb.js +94 -93
- package/src/migrate.js +24 -24
- package/src/modules/assistant.js +55 -55
- package/src/modules/bucket.js +10 -9
- package/src/modules/data.js +387 -357
- package/src/modules/file.js +3 -6
- package/src/modules/mongodb.js +6 -7
- package/src/modules/user.js +24 -47
- package/src/modules/workflow.js +68 -67
- package/src/openai.jobs.js +2 -2
- package/src/packs.js +30 -31
- package/src/providers.js +101 -3
- package/src/setenv.js +1 -1
- package/src/user.js +0 -17
- package/src/workers/crypto-worker.js +21 -20
- package/src/workers/import-export-worker.js +18 -18
- package/test/data.backup.integration.test.js +7 -12
- package/test/data.integration.test.js +27 -25
- package/test/globalSetup.js +2 -2
- package/test/import_export.integration.test.js +6 -6
- package/test/model.integration.test.js +21 -22
- package/test/workflow.integration.test.js +2 -5
- package/test/workflow.robustness.test.js +6 -13
- 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 "{params.user}" 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;
|