data-primals-engine 1.6.1 → 1.6.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.
- package/README.md +73 -42
- package/client/package-lock.json +524 -5
- package/client/package.json +2 -1
- package/client/src/App.scss +14 -1
- package/client/src/Dashboard.jsx +4 -0
- package/client/src/DataLayout.jsx +85 -20
- package/client/src/DataLayout.scss +32 -6
- package/client/src/DataTable.jsx +18 -11
- package/client/src/ModelCreator.jsx +686 -683
- package/client/src/ModelImporter.jsx +1 -1
- package/client/src/ViewSwitcher.jsx +1 -1
- package/client/src/WorkflowEditor.jsx +317 -0
- package/client/src/WorkflowEditor.scss +16 -0
- package/package.json +22 -19
- package/src/client.js +4 -0
- package/src/defaultModels.js +5 -2
- package/src/i18n.js +35 -3
- package/src/modules/assistant/assistant.js +38 -56
- package/src/modules/assistant/providers.js +38 -0
- package/src/modules/data/data.history.js +2 -5
- package/src/modules/data/data.operations.js +3496 -3484
- package/src/modules/workflow.js +3 -6
- package/src/packs.js +239 -24
- package/test/assistant.test.js +207 -0
- package/test/config.test.js +41 -0
- package/test/data.history.integration.test.js +4 -4
|
@@ -1,684 +1,687 @@
|
|
|
1
|
-
|
|
2
|
-
import React, {forwardRef, useEffect, useRef, useState} from 'react';
|
|
3
|
-
import {useMutation, useQuery, useQueryClient} from 'react-query';
|
|
4
|
-
import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
5
|
-
import {FaArrowDown, FaArrowUp, FaEdit, FaInfo, FaMinus, FaPlus, FaSave, FaTrash} from "react-icons/fa";
|
|
6
|
-
|
|
7
|
-
import "./ModelCreator.scss"
|
|
8
|
-
import {CheckboxField, CodeField, ColorField, IconField, NumberField, SelectField, TextField} from "./Field.jsx";
|
|
9
|
-
import Button from "./Button.jsx";
|
|
10
|
-
import {Trans, useTranslation} from "react-i18next";
|
|
11
|
-
import {
|
|
12
|
-
allowedFields,
|
|
13
|
-
defaultMaxRequestData,
|
|
14
|
-
mainFieldsTypes, maxFileSize,
|
|
15
|
-
maxModelNameLength,
|
|
16
|
-
maxRequestData
|
|
17
|
-
} from "../../src/constants.js";
|
|
18
|
-
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
19
|
-
import {getUserHash, getUserId, isLocalUser} from "../../src/data.js";
|
|
20
|
-
import {useNotificationContext} from "./NotificationProvider.jsx";
|
|
21
|
-
import ConditionBuilder from './ConditionBuilder.jsx';
|
|
22
|
-
import './ConditionBuilder.scss';
|
|
23
|
-
import {Tooltip} from "react-tooltip";
|
|
24
|
-
import {FaCircleInfo} from "react-icons/fa6";
|
|
25
|
-
import i18n from "../../src/i18n.js";
|
|
26
|
-
import Draggable from "./Draggable.jsx";
|
|
27
|
-
import FlexBuilder from "./FlexBuilder.jsx";
|
|
28
|
-
import {NavLink} from "react-router";
|
|
29
|
-
import CalculationBuilder from "./CalculationBuilder.jsx";
|
|
30
|
-
import CronBuilder from "./CronBuilder.jsx";
|
|
31
|
-
import ModelCreatorField from "./ModelCreatorField.jsx";
|
|
32
|
-
import useLocalStorage from "./hooks/useLocalStorage.js";
|
|
33
|
-
|
|
34
|
-
const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGenerate = false, initialModel, onModelSaved }, ref) => {
|
|
35
|
-
|
|
36
|
-
const queryClient = useQueryClient();
|
|
37
|
-
const {t, i18n} = useTranslation();
|
|
38
|
-
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
39
|
-
|
|
40
|
-
const {models, elementsPerPage, page, setDatasToLoad, selectedModel, setSelectedModel, pagedFilters, relationIds, pagedSort, setGeneratedModels, generatedModels } = useModelContext();
|
|
41
|
-
const [modelName, setModelName] = useState(initialModel?.name || ''); // Utilisation de initialModel
|
|
42
|
-
const [modelMaxRequestData, setModelMaxRequestData] = useState(initialModel?.maxRequestData || ''); // Utilisation de initialModel
|
|
43
|
-
const [modelDescription, setModelDescription] = useState(initialModel?.name ? t(`model_description_${initialModel?.name}`, initialModel?.description) : '');
|
|
44
|
-
const [modelHistory, setModelHistory] = useState(!!initialModel?.history || undefined);
|
|
45
|
-
const [modelIcon, setModelIcon] = useState(!!initialModel?.icon || undefined);
|
|
46
|
-
const [fields, setFields] = useState([]);
|
|
47
|
-
const [changed, setChanged] = useState(false);
|
|
48
|
-
|
|
49
|
-
const [selectedGeneratedModelIndex, setSelectedGeneratedModelIndex] = useState(0); // Pour l'index du modèle choisi
|
|
50
|
-
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
if (initialModel) {
|
|
53
|
-
// Mode édition : on charge les données du modèle existant
|
|
54
|
-
setModelName(initialModel.name || '');
|
|
55
|
-
setModelMaxRequestData(initialModel.maxRequestData || defaultMaxRequestData);
|
|
56
|
-
setModelDescription(initialModel.name ? t(`model_description_${initialModel.name}`, initialModel.description) : '');
|
|
57
|
-
setFields([...(initialModel.fields || []).map(m => ({...m}))]);
|
|
58
|
-
setModelHistory(initialModel.history);
|
|
59
|
-
setModelIcon(initialModel.icon);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
case '
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
case '
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
case '
|
|
243
|
-
case '
|
|
244
|
-
case '
|
|
245
|
-
case '
|
|
246
|
-
case '
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
case '
|
|
266
|
-
otherFields = ['
|
|
267
|
-
break;
|
|
268
|
-
case '
|
|
269
|
-
otherFields = ['
|
|
270
|
-
break;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
newFields[
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const [
|
|
369
|
-
const [
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
modelsList.
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
{
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
{
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
<
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
<
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
<
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1
|
+
|
|
2
|
+
import React, {forwardRef, useEffect, useRef, useState} from 'react';
|
|
3
|
+
import {useMutation, useQuery, useQueryClient} from 'react-query';
|
|
4
|
+
import {useModelContext} from "./contexts/ModelContext.jsx";
|
|
5
|
+
import {FaArrowDown, FaArrowUp, FaEdit, FaInfo, FaMinus, FaPlus, FaSave, FaTrash} from "react-icons/fa";
|
|
6
|
+
|
|
7
|
+
import "./ModelCreator.scss"
|
|
8
|
+
import {CheckboxField, CodeField, ColorField, IconField, NumberField, SelectField, TextField} from "./Field.jsx";
|
|
9
|
+
import Button from "./Button.jsx";
|
|
10
|
+
import {Trans, useTranslation} from "react-i18next";
|
|
11
|
+
import {
|
|
12
|
+
allowedFields,
|
|
13
|
+
defaultMaxRequestData,
|
|
14
|
+
mainFieldsTypes, maxFileSize,
|
|
15
|
+
maxModelNameLength,
|
|
16
|
+
maxRequestData
|
|
17
|
+
} from "../../src/constants.js";
|
|
18
|
+
import {useAuthContext} from "./contexts/AuthContext.jsx";
|
|
19
|
+
import {getUserHash, getUserId, isLocalUser} from "../../src/data.js";
|
|
20
|
+
import {useNotificationContext} from "./NotificationProvider.jsx";
|
|
21
|
+
import ConditionBuilder from './ConditionBuilder.jsx';
|
|
22
|
+
import './ConditionBuilder.scss';
|
|
23
|
+
import {Tooltip} from "react-tooltip";
|
|
24
|
+
import {FaCircleInfo} from "react-icons/fa6";
|
|
25
|
+
import i18n from "../../src/i18n.js";
|
|
26
|
+
import Draggable from "./Draggable.jsx";
|
|
27
|
+
import FlexBuilder from "./FlexBuilder.jsx";
|
|
28
|
+
import {NavLink} from "react-router";
|
|
29
|
+
import CalculationBuilder from "./CalculationBuilder.jsx";
|
|
30
|
+
import CronBuilder from "./CronBuilder.jsx";
|
|
31
|
+
import ModelCreatorField from "./ModelCreatorField.jsx";
|
|
32
|
+
import useLocalStorage from "./hooks/useLocalStorage.js";
|
|
33
|
+
|
|
34
|
+
const ModelCreator = forwardRef(({ initialPrompt = '', onModelGenerated, autoGenerate = false, initialModel, onModelSaved }, ref) => {
|
|
35
|
+
|
|
36
|
+
const queryClient = useQueryClient();
|
|
37
|
+
const {t, i18n} = useTranslation();
|
|
38
|
+
const lang = (i18n.resolvedLanguage || i18n.language).split(/[-_]/)?.[0];
|
|
39
|
+
|
|
40
|
+
const {models, elementsPerPage, page, setDatasToLoad, selectedModel, setSelectedModel, pagedFilters, relationIds, pagedSort, setGeneratedModels, generatedModels } = useModelContext();
|
|
41
|
+
const [modelName, setModelName] = useState(initialModel?.name || ''); // Utilisation de initialModel
|
|
42
|
+
const [modelMaxRequestData, setModelMaxRequestData] = useState(initialModel?.maxRequestData || ''); // Utilisation de initialModel
|
|
43
|
+
const [modelDescription, setModelDescription] = useState(initialModel?.name ? t(`model_description_${initialModel?.name}`, initialModel?.description) : '');
|
|
44
|
+
const [modelHistory, setModelHistory] = useState(!!initialModel?.history || undefined);
|
|
45
|
+
const [modelIcon, setModelIcon] = useState(!!initialModel?.icon || undefined);
|
|
46
|
+
const [fields, setFields] = useState([]);
|
|
47
|
+
const [changed, setChanged] = useState(false);
|
|
48
|
+
|
|
49
|
+
const [selectedGeneratedModelIndex, setSelectedGeneratedModelIndex] = useState(0); // Pour l'index du modèle choisi
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (initialModel) {
|
|
53
|
+
// Mode édition : on charge les données du modèle existant
|
|
54
|
+
setModelName(initialModel.name || '');
|
|
55
|
+
setModelMaxRequestData(initialModel.maxRequestData || defaultMaxRequestData);
|
|
56
|
+
setModelDescription(initialModel.name ? t(`model_description_${initialModel.name}`, initialModel.description) : '');
|
|
57
|
+
setFields([...(initialModel.fields || []).map(m => ({...m}))]);
|
|
58
|
+
setModelHistory(initialModel.history);
|
|
59
|
+
setModelIcon(initialModel.icon);
|
|
60
|
+
setUseAI(false);
|
|
61
|
+
setPrompt(null);
|
|
62
|
+
setHomePrompt(null);
|
|
63
|
+
} else {
|
|
64
|
+
// Mode création : on réinitialise tout pour une nouvelle génération
|
|
65
|
+
setModelName('');
|
|
66
|
+
setModelDescription('');
|
|
67
|
+
setFields([]);
|
|
68
|
+
setUseAI(true); // On active l'IA par défaut
|
|
69
|
+
setModelHistory(false);
|
|
70
|
+
setModelIcon(null);
|
|
71
|
+
}
|
|
72
|
+
}, [initialModel]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (generatedModels.length > 0 && selectedGeneratedModelIndex >= 0) {
|
|
76
|
+
const selectedModel = generatedModels[selectedGeneratedModelIndex];
|
|
77
|
+
if (selectedModel) {
|
|
78
|
+
setModelName(selectedModel.name || '');
|
|
79
|
+
setModelDescription(selectedModel.description || '');
|
|
80
|
+
setFields(selectedModel.fields || []);
|
|
81
|
+
setModelMaxRequestData(selectedModel.maxRequestData || defaultMaxRequestData);
|
|
82
|
+
setModelIcon(selectedModel.icon || null);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, [generatedModels, selectedGeneratedModelIndex]);
|
|
86
|
+
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if( !changed)
|
|
89
|
+
setModelDescription(initialModel?.name ? t('model_description_'+initialModel?.name, initialModel?.description) : '');
|
|
90
|
+
}, [lang]);
|
|
91
|
+
|
|
92
|
+
const mutationRename = useMutation(
|
|
93
|
+
({oldName, newName}) => {
|
|
94
|
+
const url = `/api/model/${initialModel._id}/renameField?_user=${encodeURIComponent(getUserId(me))}`;
|
|
95
|
+
return fetch(url, {
|
|
96
|
+
method: 'PATCH',
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
body: JSON.stringify({ oldFieldName: oldName, newFieldName: newName }),
|
|
99
|
+
}).then((res) => res.json());
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
onSuccess: (data) => {
|
|
103
|
+
setDatasToLoad(datas=>[...datas, modelName]);
|
|
104
|
+
queryClient.invalidateQueries('api/models');
|
|
105
|
+
queryClient.invalidateQueries(['api/data', modelName, 'page', page, elementsPerPage, pagedFilters[modelName], pagedSort[modelName]]);
|
|
106
|
+
},
|
|
107
|
+
onError: (error) => {
|
|
108
|
+
console.error('Erreur:', error);
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const { me } = useAuthContext()
|
|
114
|
+
|
|
115
|
+
const {addNotification} = useNotificationContext()
|
|
116
|
+
|
|
117
|
+
const mutation = useMutation(
|
|
118
|
+
(modelData) => {
|
|
119
|
+
const url = initialModel?._id ? `/api/model/${initialModel._id}` : '/api/model'; // Utilisation de initialModel pour l'URL
|
|
120
|
+
const method = initialModel?._id ? 'PUT' : 'POST';
|
|
121
|
+
return fetch(url+'?_user='+me.username, {
|
|
122
|
+
method: method,
|
|
123
|
+
headers: { 'Content-Type': 'application/json' },
|
|
124
|
+
body: JSON.stringify(modelData),
|
|
125
|
+
}).then((res) => res.json());
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
onSuccess: (data) => {
|
|
129
|
+
|
|
130
|
+
const notificationData = {
|
|
131
|
+
title: data.success ? t('modelcreator.success', 'Modèle sauvegardé') : t(data.error, data.error),
|
|
132
|
+
icon: data.success ? <FaInfo /> : undefined,
|
|
133
|
+
status: data.success ? 'completed': 'error'
|
|
134
|
+
};
|
|
135
|
+
addNotification(notificationData);
|
|
136
|
+
|
|
137
|
+
if (!initialModel) {
|
|
138
|
+
setModelName('');
|
|
139
|
+
setModelDescription('');
|
|
140
|
+
setModelMaxRequestData(defaultMaxRequestData);
|
|
141
|
+
setFields([{ name: '', type: 'string' }]);
|
|
142
|
+
setModelHistory(undefined);
|
|
143
|
+
setModelIcon(null);
|
|
144
|
+
}
|
|
145
|
+
setDatasToLoad(datas=>[...datas, modelName]);
|
|
146
|
+
|
|
147
|
+
queryClient.invalidateQueries(['api/data', modelName, 'page', page, elementsPerPage, pagedFilters[modelName], pagedSort[modelName]]);
|
|
148
|
+
queryClient.invalidateQueries('api/models');
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
},
|
|
152
|
+
onError: (error) => {
|
|
153
|
+
const notificationData = {
|
|
154
|
+
title: t(error, error),
|
|
155
|
+
status: 'error'
|
|
156
|
+
};
|
|
157
|
+
addNotification(notificationData);
|
|
158
|
+
console.error('Erreur:', error);
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const handleAddField = () => {
|
|
164
|
+
setFields([...fields, { name: '', type: 'string', _isNewField: true }]);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const handleRemoveField = (index) => {
|
|
168
|
+
const newFields = [...fields];
|
|
169
|
+
newFields.splice(index, 1);
|
|
170
|
+
setFields(newFields);
|
|
171
|
+
};
|
|
172
|
+
const deleteMutation = useMutation(
|
|
173
|
+
(modelName) =>
|
|
174
|
+
fetch(`/api/model?name=${modelName}&_user=${encodeURIComponent(getUserId(me))}`, {
|
|
175
|
+
method: 'DELETE',
|
|
176
|
+
}).then((res) => res.json()),
|
|
177
|
+
{
|
|
178
|
+
onSuccess: () => {
|
|
179
|
+
queryClient.invalidateQueries('api/models');
|
|
180
|
+
if (onModelSaved) {
|
|
181
|
+
onModelSaved(); // Vous pouvez passer un argument ici si nécessaire
|
|
182
|
+
}
|
|
183
|
+
// Réinitialiser le formulaire ou rediriger, selon vos besoins
|
|
184
|
+
setModelName('');
|
|
185
|
+
setModelDescription('');
|
|
186
|
+
setSelectedModel(null)
|
|
187
|
+
setModelHistory(undefined)
|
|
188
|
+
setModelIcon(null);
|
|
189
|
+
setFields([{ name: '', type: 'string', _isNewField: true }]);
|
|
190
|
+
setDatasToLoad(datas => datas.filter(f => f !== modelName));
|
|
191
|
+
setGeneratedModels(mods => {
|
|
192
|
+
return mods.filter(m => m.name !== initialModel.name);
|
|
193
|
+
});
|
|
194
|
+
const notificationData = {
|
|
195
|
+
title: t('modelcreator.deleteSuccess', 'Modèle supprimé'),
|
|
196
|
+
icon: <FaInfo />,
|
|
197
|
+
status: 'completed'
|
|
198
|
+
};
|
|
199
|
+
addNotification(notificationData);
|
|
200
|
+
},
|
|
201
|
+
onError: (error) => {
|
|
202
|
+
console.error('Erreur lors de la suppression:', error);
|
|
203
|
+
|
|
204
|
+
const notificationData = {
|
|
205
|
+
title: t(error, error),
|
|
206
|
+
status: 'error'
|
|
207
|
+
};
|
|
208
|
+
addNotification(notificationData);
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const handleDelete = () => {
|
|
214
|
+
if (initialModel && window.confirm("Êtes-vous sûr de vouloir supprimer ce modèle ?")) {
|
|
215
|
+
deleteMutation.mutate(initialModel.name);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const handleSubmit = (e) => {
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
const newModel = {
|
|
221
|
+
name: modelName,
|
|
222
|
+
description: modelDescription,
|
|
223
|
+
maxRequestData: modelMaxRequestData,
|
|
224
|
+
history: modelHistory,
|
|
225
|
+
icon: modelIcon,
|
|
226
|
+
fields: (fields || []).map((field) => {
|
|
227
|
+
delete field['_isNewField'];
|
|
228
|
+
let otherFields = [];
|
|
229
|
+
// Check for specific field types
|
|
230
|
+
switch ((field.type)) {
|
|
231
|
+
case 'relation':
|
|
232
|
+
otherFields = ['relation', 'multiple', 'relationFilter'];
|
|
233
|
+
break;
|
|
234
|
+
case 'enum':
|
|
235
|
+
{
|
|
236
|
+
otherFields = ['items']
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
case 'number':
|
|
240
|
+
otherFields = ['min', 'max', 'step', 'unit','delay', 'gauge', 'percent'];
|
|
241
|
+
break;
|
|
242
|
+
case 'string':
|
|
243
|
+
case 'string_t':
|
|
244
|
+
case 'richtext':
|
|
245
|
+
case 'url':
|
|
246
|
+
case 'email':
|
|
247
|
+
case 'phone':
|
|
248
|
+
case 'password':
|
|
249
|
+
case 'code':
|
|
250
|
+
const t = (field.itemsType || field.type);
|
|
251
|
+
if (t === 'code')
|
|
252
|
+
otherFields = ['maxlength', 'language', 'conditionBuilder'];
|
|
253
|
+
else if( ['string_t', 'string'].includes(t))
|
|
254
|
+
otherFields = ['maxlength', 'multiline', 'mask', 'replacement'];
|
|
255
|
+
else
|
|
256
|
+
otherFields = ['maxlength'];
|
|
257
|
+
break;
|
|
258
|
+
case 'date':
|
|
259
|
+
case 'datetime':
|
|
260
|
+
{
|
|
261
|
+
otherFields = ['min','max'];
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
case 'image':
|
|
265
|
+
case 'file':
|
|
266
|
+
otherFields = ['mimeTypes', 'maxSize'];
|
|
267
|
+
break;
|
|
268
|
+
case 'calculated':
|
|
269
|
+
otherFields = ['calculation'];
|
|
270
|
+
break;
|
|
271
|
+
case 'array':
|
|
272
|
+
otherFields = ['itemsType'];
|
|
273
|
+
break;
|
|
274
|
+
default:
|
|
275
|
+
}
|
|
276
|
+
Object.keys(field).forEach(f => {
|
|
277
|
+
if( ![...allowedFields, ...otherFields].includes(f))
|
|
278
|
+
delete field[f];
|
|
279
|
+
});
|
|
280
|
+
return field;
|
|
281
|
+
}),
|
|
282
|
+
};
|
|
283
|
+
setGeneratedModels(gen => gen.filter(m => m.name !== newModel.name));
|
|
284
|
+
mutation.mutateAsync(newModel).then(data => {
|
|
285
|
+
queryClient.invalidateQueries('api/models');
|
|
286
|
+
newModel.fields.forEach(field => {
|
|
287
|
+
if( field.type === "relation") {
|
|
288
|
+
queryClient.invalidateQueries(['api/data', field.relation, relationIds[field.relation]]);
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
if (onModelSaved) {
|
|
292
|
+
onModelSaved(data.data);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
};
|
|
297
|
+
const swapElements = (array, index1, index2) => {
|
|
298
|
+
if( index1 < 0 || index2 < 0 || index1 >= array.length || index2 >= array.length)
|
|
299
|
+
return;
|
|
300
|
+
let temp = array[index1];
|
|
301
|
+
array[index1] = array[index2];
|
|
302
|
+
array[index2] = temp;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const handleUp = (index) => {
|
|
306
|
+
const nf = [...fields];
|
|
307
|
+
swapElements(nf, index, index -1);
|
|
308
|
+
setFields(nf);
|
|
309
|
+
};
|
|
310
|
+
const handleDown = (index) => {
|
|
311
|
+
const nf = [...fields];
|
|
312
|
+
swapElements(nf, index, index + 1);
|
|
313
|
+
setFields(nf);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const handleAddValue = (fi) => {
|
|
317
|
+
const newFields = [...fields];
|
|
318
|
+
if( !newFields[fi].items )
|
|
319
|
+
newFields[fi].items = [];
|
|
320
|
+
newFields[fi].items.push("");
|
|
321
|
+
setFields(newFields)
|
|
322
|
+
}
|
|
323
|
+
const handleRemoveValue = (fi, index) => {
|
|
324
|
+
const newFields = [...fields];
|
|
325
|
+
const field = newFields[fi];
|
|
326
|
+
field.items = field.items.filter((f,i) => i !== index);
|
|
327
|
+
setFields(newFields)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const handleRenameField = (fi, oldVal) => {
|
|
331
|
+
const prompted = window.prompt(t('core.renameFields', 'Renommer le champ'), oldVal);
|
|
332
|
+
if (prompted && prompted !== oldVal) {
|
|
333
|
+
mutationRename.mutateAsync({oldName: oldVal, newName: prompted}).then(e =>{
|
|
334
|
+
const newFields = [...fields];
|
|
335
|
+
const field = newFields[fi];
|
|
336
|
+
field.name = prompted;
|
|
337
|
+
setFields(newFields)
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
const modelLocked = !!initialModel && (!initialModel?._user ? true : isLocalUser(me) && initialModel?.locked);
|
|
344
|
+
|
|
345
|
+
const hint = (description) => t(description, '') && <div className="hint-icon"><FaCircleInfo data-tooltip-id={`tooltipHint`} data-tooltip-content={description} /></div>
|
|
346
|
+
|
|
347
|
+
const handleMoveDown = (field, index, i) => {
|
|
348
|
+
const newFields = [...fields];
|
|
349
|
+
if( i + 1 >= newFields[index].items.length )
|
|
350
|
+
return;
|
|
351
|
+
const r = newFields[index].items[i + 1];
|
|
352
|
+
newFields[index].items[i + 1] = newFields[index].items[i];
|
|
353
|
+
newFields[index].items[i] = r;
|
|
354
|
+
setFields(newFields);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const handleMoveUp = (field, index, i) => {
|
|
358
|
+
const newFields = [...fields];
|
|
359
|
+
if( i < 1 )
|
|
360
|
+
return;
|
|
361
|
+
const r = newFields[index].items[i - 1];
|
|
362
|
+
newFields[index].items[i - 1] = newFields[index].items[i];
|
|
363
|
+
newFields[index].items[i] = r;
|
|
364
|
+
setFields(newFields);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const [useAI, setUseAI] = useState(true);
|
|
368
|
+
const [showModel, setModelVisible] = useState(false);
|
|
369
|
+
const [prompt, setPrompt] = useState('');
|
|
370
|
+
|
|
371
|
+
const [homePrompt, setHomePrompt] = useLocalStorage("ai_model_prompt", null);
|
|
372
|
+
const [promptResult, setPromptResult] = useLocalStorage("ai_model_prompt_result", null);
|
|
373
|
+
|
|
374
|
+
// NOUVEAU : Créer une référence pour suivre le déclenchement
|
|
375
|
+
const hasTriggeredAutoGenerate = useRef(false);
|
|
376
|
+
|
|
377
|
+
const[generationIsLoading, setGenerationIsLoading] =useState(false);
|
|
378
|
+
|
|
379
|
+
const generateModelMutation = useMutation(
|
|
380
|
+
async ({userPrompt, modelToEdit}) => {
|
|
381
|
+
const bodyPayload = { prompt: userPrompt };
|
|
382
|
+
// Si on est en mode édition, on ajoute le modèle au corps de la requête
|
|
383
|
+
if (modelToEdit) {
|
|
384
|
+
bodyPayload.existingModel = modelToEdit;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const response = await fetch(`/api/model/generate?lang=${lang}`, {
|
|
388
|
+
method: 'POST',
|
|
389
|
+
headers: { 'Content-Type': 'application/json' },
|
|
390
|
+
body: JSON.stringify(bodyPayload),
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (!response.ok) {
|
|
394
|
+
const err = await response.json();
|
|
395
|
+
throw new Error(err.error || 'Failed to generate model');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return response.json();
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
onSuccess: (data) => {
|
|
402
|
+
const modelsList = data?.models || data || [];
|
|
403
|
+
|
|
404
|
+
if (modelsList && Array.isArray(modelsList) && modelsList.length > 0) {
|
|
405
|
+
const mds = modelsList.map(m => ({ ...m, _user: getUserId(me) }));
|
|
406
|
+
setGeneratedModels(g=>[...g, ...mds]);
|
|
407
|
+
onModelGenerated?.(mds);
|
|
408
|
+
modelsList.forEach(d => gtag('event', 'model generation "' + d.name + '"'));
|
|
409
|
+
|
|
410
|
+
setSelectedGeneratedModelIndex(0); // Sélectionne le premier par défaut
|
|
411
|
+
setModelVisible(true); // Affiche la section avec la liste et le formulaire
|
|
412
|
+
setHomePrompt(null);
|
|
413
|
+
addNotification({ title: t('modelcreator.generate.success'), status: 'completed' });
|
|
414
|
+
} else {
|
|
415
|
+
// GA ne retourne aucun modèle
|
|
416
|
+
setModelVisible(false);
|
|
417
|
+
setHomePrompt(null);
|
|
418
|
+
addNotification({
|
|
419
|
+
title: t('modelcreator.generate.error'),
|
|
420
|
+
description: t('modelcreator.generate.no_models', "Aucun modèle n'a pu être généré."),
|
|
421
|
+
status: 'error'
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
setGenerationIsLoading(false);
|
|
425
|
+
},
|
|
426
|
+
onError: (error) => {
|
|
427
|
+
setPromptResult(false);
|
|
428
|
+
addNotification({
|
|
429
|
+
title: t('modelcreator.generate.error'),
|
|
430
|
+
description: error.message,
|
|
431
|
+
status: 'error'
|
|
432
|
+
});
|
|
433
|
+
},
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const handleGenerate = () => {
|
|
438
|
+
if( prompt?.trim() ){
|
|
439
|
+
gtag("event", "generate model by AI");
|
|
440
|
+
setPromptResult(null);
|
|
441
|
+
setGenerationIsLoading(true);
|
|
442
|
+
generateModelMutation.mutate({
|
|
443
|
+
userPrompt: prompt,
|
|
444
|
+
modelToEdit: initialModel
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
// on ne veut la génération du prompt que si il y a un prompt en attente
|
|
451
|
+
if(homePrompt && !hasTriggeredAutoGenerate.current){
|
|
452
|
+
setPrompt(homePrompt);
|
|
453
|
+
hasTriggeredAutoGenerate.current = true;
|
|
454
|
+
setUseAI(true);
|
|
455
|
+
setGenerationIsLoading(true);
|
|
456
|
+
generateModelMutation.mutate({
|
|
457
|
+
userPrompt: homePrompt,
|
|
458
|
+
modelToEdit: initialModel
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
}, [initialModel]); // Le tableau vide est toujours correct
|
|
463
|
+
return (
|
|
464
|
+
<div className="model-creator" ref={ref}>
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
<Tooltip id={"tooltipHint"}
|
|
468
|
+
place={"top-end"}
|
|
469
|
+
globalCloseEvents={{ clickOutsideAnchor: true }}
|
|
470
|
+
render={({ content, activeAnchor }) => {
|
|
471
|
+
gtag('render hint ' + content);
|
|
472
|
+
const c = t(content, content);
|
|
473
|
+
return c && (
|
|
474
|
+
<><p className="ws-pre-line">{c}</p></>)
|
|
475
|
+
}} />
|
|
476
|
+
<h2 className={"field-bg p-2"}>
|
|
477
|
+
{!initialModel && <Trans i18nKey={"btns.createModel"}>Créer un modèle</Trans>}
|
|
478
|
+
{!!initialModel && <><Trans i18nKey={"btns.editModel"}>Editer le modèle</Trans> "{t(`model_${modelName}`, modelName)}"</>}
|
|
479
|
+
</h2>
|
|
480
|
+
<form onSubmit={handleSubmit}>
|
|
481
|
+
|
|
482
|
+
{/* Section pour choisir le mode de création (IA ou Manuel) */}
|
|
483
|
+
{!initialModel && (
|
|
484
|
+
<CheckboxField
|
|
485
|
+
label={<Trans i18nKey="modelcreator.useAI" />}
|
|
486
|
+
checked={useAI}
|
|
487
|
+
onChange={() => setUseAI(!useAI)}
|
|
488
|
+
/>
|
|
489
|
+
)}
|
|
490
|
+
|
|
491
|
+
{/* Section pour le prompt de l'IA (uniquement si création par IA et avant génération) */}
|
|
492
|
+
{useAI && !selectedModel && !showModel && (
|
|
493
|
+
<>
|
|
494
|
+
<div className="ai-prompt-container">
|
|
495
|
+
<>
|
|
496
|
+
<TextField
|
|
497
|
+
value={prompt}
|
|
498
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
499
|
+
multiline
|
|
500
|
+
help={t("modelcreator.generate.help")}
|
|
501
|
+
required
|
|
502
|
+
disabled={generationIsLoading}
|
|
503
|
+
/>
|
|
504
|
+
<div className="examples" dangerouslySetInnerHTML={{ __html: t('modelcreator.generate.examples') }} />
|
|
505
|
+
<Button
|
|
506
|
+
role={"button"}
|
|
507
|
+
onClick={handleGenerate}
|
|
508
|
+
disabled={generationIsLoading}
|
|
509
|
+
>
|
|
510
|
+
{generationIsLoading
|
|
511
|
+
? <Trans i18nKey="modelcreator.generating" />
|
|
512
|
+
: <Trans i18nKey="modelcreator.generate" />
|
|
513
|
+
}
|
|
514
|
+
</Button>
|
|
515
|
+
</>
|
|
516
|
+
</div>
|
|
517
|
+
{generationIsLoading && <p><Trans i18nKey="modelcreator.generating" /></p>}
|
|
518
|
+
</>
|
|
519
|
+
)}
|
|
520
|
+
|
|
521
|
+
{/* Layout principal pour afficher la liste et le formulaire côte à côte APRES génération */}
|
|
522
|
+
{useAI && showModel && !initialModel && (
|
|
523
|
+
<div className="flex model-generation-layout">
|
|
524
|
+
{/* Colonne de gauche: Liste des modèles générés */}
|
|
525
|
+
{generatedModels.some(g => models.find(f => f.name === g.name && f._user === g._user)) && (
|
|
526
|
+
<>
|
|
527
|
+
<div className="generated-models-list">
|
|
528
|
+
<h4>{t('modelcreator.generate.results_title', 'Suggestions de l\'IA')}</h4>
|
|
529
|
+
<ul>
|
|
530
|
+
{generatedModels.map((model, index) => (
|
|
531
|
+
<li
|
|
532
|
+
key={index}
|
|
533
|
+
className={index === selectedGeneratedModelIndex ? 'active' : ''}
|
|
534
|
+
onClick={() => setSelectedGeneratedModelIndex(index)}
|
|
535
|
+
>
|
|
536
|
+
{model.name}
|
|
537
|
+
</li>
|
|
538
|
+
))}
|
|
539
|
+
</ul>
|
|
540
|
+
</div>
|
|
541
|
+
|
|
542
|
+
{/* Colonne de droite: Formulaire du modèle sélectionné */}
|
|
543
|
+
<div className="model-form-container">
|
|
544
|
+
{/* Le formulaire existant est placé ici */}
|
|
545
|
+
<div className="field">
|
|
546
|
+
<div className="flex field-bg">
|
|
547
|
+
<label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
|
|
548
|
+
</div>
|
|
549
|
+
<TextField
|
|
550
|
+
type="text"
|
|
551
|
+
id="modelName"
|
|
552
|
+
disabled={modelLocked}
|
|
553
|
+
value={modelName}
|
|
554
|
+
help={t('modelcreator.field.name.hint')}
|
|
555
|
+
onChange={(e) => setModelName(e.target.value)}
|
|
556
|
+
required
|
|
557
|
+
/>
|
|
558
|
+
</div>
|
|
559
|
+
|
|
560
|
+
<div className="field">
|
|
561
|
+
<div className="checkbox-label flex flex-1">
|
|
562
|
+
<CheckboxField
|
|
563
|
+
label={<Trans i18nKey={"history"}>Historique</Trans>}
|
|
564
|
+
help={t('modelcreator.field.history', '')}
|
|
565
|
+
disabled={modelLocked}
|
|
566
|
+
checked={!!modelHistory}
|
|
567
|
+
onChange={(e) => {
|
|
568
|
+
setModelHistory(e? { enabled: true }: false);
|
|
569
|
+
}}
|
|
570
|
+
/>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
<h3><Trans i18nKey={"modelcreator.fields"}>Champs du modèle :</Trans></h3>
|
|
576
|
+
{fields.map((field, index) => <ModelCreatorField
|
|
577
|
+
key={initialModel?.name + '_field_' + index}
|
|
578
|
+
handleRemoveValue={handleRemoveValue}
|
|
579
|
+
handleAddValue={handleAddValue}
|
|
580
|
+
handleRenameField={handleRenameField}
|
|
581
|
+
handleUp={handleUp}
|
|
582
|
+
handleDown={handleDown}
|
|
583
|
+
handleRemoveField={handleRemoveField}
|
|
584
|
+
index={index} fields={fields} field={field} model={initialModel} setFields={setFields} />)}
|
|
585
|
+
</div></>)}
|
|
586
|
+
</div>
|
|
587
|
+
)}
|
|
588
|
+
|
|
589
|
+
{/* Affichage du formulaire en mode manuel ou édition */}
|
|
590
|
+
{(!useAI || !!initialModel) && (
|
|
591
|
+
<div className="model-form-container">
|
|
592
|
+
<div className="flex field-bg">
|
|
593
|
+
<label htmlFor="modelName"><Trans i18nKey={"modelcreator.name"}>Nom:</Trans></label>
|
|
594
|
+
</div>
|
|
595
|
+
<TextField
|
|
596
|
+
type="text"
|
|
597
|
+
id="modelName"
|
|
598
|
+
disabled={modelLocked}
|
|
599
|
+
value={modelName}
|
|
600
|
+
help={t('modelcreator.field.name.hint')}
|
|
601
|
+
onChange={(e) => setModelName(e.target.value)}
|
|
602
|
+
required
|
|
603
|
+
/>
|
|
604
|
+
|
|
605
|
+
<div className="flex field-bg">
|
|
606
|
+
<label htmlFor="modelDescription"><Trans i18nKey={"modelcreator.description"}>Description:</Trans></label>
|
|
607
|
+
</div>
|
|
608
|
+
<TextField
|
|
609
|
+
multiline
|
|
610
|
+
help={t('modelcreator.field.description')}
|
|
611
|
+
id="modelDescription"
|
|
612
|
+
disabled={modelLocked}
|
|
613
|
+
value={modelDescription}
|
|
614
|
+
onChange={(e) => {
|
|
615
|
+
setModelDescription(e.target.value);
|
|
616
|
+
setChanged(true)
|
|
617
|
+
}}
|
|
618
|
+
/>
|
|
619
|
+
|
|
620
|
+
<div className="flex field-bg">
|
|
621
|
+
<label htmlFor="modelIcon"><Trans i18nKey={"modelcreator.icon"}>Icône:</Trans></label>
|
|
622
|
+
</div>
|
|
623
|
+
<IconField
|
|
624
|
+
help={t('modelcreator.field.icon')}
|
|
625
|
+
id="modelIcon"
|
|
626
|
+
disabled={modelLocked}
|
|
627
|
+
value={modelIcon}
|
|
628
|
+
onChange={(e) => {
|
|
629
|
+
setModelIcon(e);
|
|
630
|
+
setChanged(true)
|
|
631
|
+
}}
|
|
632
|
+
/>
|
|
633
|
+
|
|
634
|
+
<div className="flex flex-no-wrap">
|
|
635
|
+
<div className="checkbox-label flex flex-1">
|
|
636
|
+
<CheckboxField
|
|
637
|
+
label={<Trans i18nKey={"history"}>Historique</Trans>}
|
|
638
|
+
help={t('modelcreator.field.history', '')}
|
|
639
|
+
disabled={modelLocked}
|
|
640
|
+
checked={!!modelHistory}
|
|
641
|
+
onChange={(e) => {
|
|
642
|
+
setModelHistory(e? { enabled: true }: false);
|
|
643
|
+
}}
|
|
644
|
+
/>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
<h3><Trans i18nKey={"modelcreator.fields"}>Champs du modèle :</Trans></h3>
|
|
649
|
+
{fields.map((field, index) => <ModelCreatorField
|
|
650
|
+
key={initialModel?.name + '_field_' + index}
|
|
651
|
+
handleRemoveValue={handleRemoveValue}
|
|
652
|
+
handleAddValue={handleAddValue}
|
|
653
|
+
handleRenameField={handleRenameField}
|
|
654
|
+
handleUp={handleUp}
|
|
655
|
+
handleDown={handleDown}
|
|
656
|
+
handleRemoveField={handleRemoveField}
|
|
657
|
+
index={index} fields={fields} field={field} model={initialModel} setFields={setFields} />)}
|
|
658
|
+
</div>
|
|
659
|
+
)}
|
|
660
|
+
|
|
661
|
+
{/* Boutons d'action, visibles si un modèle est affiché ou en mode manuel */}
|
|
662
|
+
{(showModel || !useAI || initialModel) && (
|
|
663
|
+
<div className="actions flex">
|
|
664
|
+
|
|
665
|
+
<Button type="button" onClick={handleAddField}>
|
|
666
|
+
<FaPlus /> <Trans i18nKey={"btns.addField"}>Ajouter un champ</Trans>
|
|
667
|
+
</Button>
|
|
668
|
+
|
|
669
|
+
<Button type="submit">
|
|
670
|
+
<FaSave />
|
|
671
|
+
<Trans i18nKey={"btns.saveModel"}>Enregistrer le modèle</Trans>
|
|
672
|
+
</Button>
|
|
673
|
+
{initialModel && (
|
|
674
|
+
<Button type="button" onClick={handleDelete}>
|
|
675
|
+
<FaTrash /><Trans i18nKey={"btns.del"}>Supprimer le modèle</Trans>
|
|
676
|
+
</Button>
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
679
|
+
)}
|
|
680
|
+
|
|
681
|
+
</form>
|
|
682
|
+
</div>
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
ModelCreator.displayName = "ModelCreator";
|
|
686
|
+
|
|
684
687
|
export default ModelCreator;
|